Compare commits

..

3 Commits

Author SHA1 Message Date
will.anderson 20029d36df ci: touch dist to trigger stage rebuild
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m35s
2026-05-11 18:45:57 -05:00
will.anderson 28f9ecd1a3 Merge pull request 'fix: heading and button elements pass children unescaped' (#113) from fix/force-full-rebuild into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m0s
2026-05-11 22:21:41 +00:00
will.anderson b6bb25e79e fix: heading and button elements pass children unescaped
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m32s
el_h1/h2/h3/h4 and el_button were calling el_escape() on their
content, converting any HTML children (e.g. <span class="gold">)
into literal entity text on screen.

These functions accept composed HTML children, not raw text — they
should pass the argument through like el_div/el_p/el_span do.
el_text, el_attr, el_title, el_textarea, and el_img keep escaping
(they handle actual text/attribute values, not HTML children).
2026-05-11 17:21:19 -05:00
5 changed files with 37 additions and 33 deletions
+21 -20
View File
@@ -1,3 +1,4 @@
// elhtml_impl.c — El HTML element stubs.
#include <stdint.h>
#include <stdlib.h>
#include "el_runtime.h"
@@ -19,11 +20,11 @@ el_val_t el_li(el_val_t attrs, el_val_t children);
el_val_t el_p(el_val_t attrs, el_val_t children);
el_val_t el_span(el_val_t attrs, el_val_t children);
el_val_t el_form(el_val_t attrs, el_val_t children);
el_val_t el_h1(el_val_t attrs, el_val_t text);
el_val_t el_h2(el_val_t attrs, el_val_t text);
el_val_t el_h3(el_val_t attrs, el_val_t text);
el_val_t el_h4(el_val_t attrs, el_val_t text);
el_val_t el_button(el_val_t attrs, el_val_t label);
el_val_t el_h1(el_val_t attrs, el_val_t children);
el_val_t el_h2(el_val_t attrs, el_val_t children);
el_val_t el_h3(el_val_t attrs, el_val_t children);
el_val_t el_h4(el_val_t attrs, el_val_t children);
el_val_t el_button(el_val_t attrs, el_val_t children);
el_val_t el_a(el_val_t href, el_val_t attrs, el_val_t children);
el_val_t el_input(el_val_t type_attr, el_val_t attrs);
el_val_t el_textarea(el_val_t attrs, el_val_t value);
@@ -176,43 +177,43 @@ el_val_t el_form(el_val_t attrs, el_val_t children) {
return 0;
}
el_val_t el_h1(el_val_t attrs, el_val_t text) {
el_val_t el_h1(el_val_t attrs, el_val_t children) {
if (str_eq(attrs, EL_STR(""))) {
return el_str_concat(el_str_concat(EL_STR("<h1>"), el_escape(text)), EL_STR("</h1>"));
return el_str_concat(el_str_concat(EL_STR("<h1>"), children), EL_STR("</h1>"));
}
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<h1 "), attrs), EL_STR(">")), el_escape(text)), EL_STR("</h1>"));
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<h1 "), attrs), EL_STR(">")), children), EL_STR("</h1>"));
return 0;
}
el_val_t el_h2(el_val_t attrs, el_val_t text) {
el_val_t el_h2(el_val_t attrs, el_val_t children) {
if (str_eq(attrs, EL_STR(""))) {
return el_str_concat(el_str_concat(EL_STR("<h2>"), el_escape(text)), EL_STR("</h2>"));
return el_str_concat(el_str_concat(EL_STR("<h2>"), children), EL_STR("</h2>"));
}
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<h2 "), attrs), EL_STR(">")), el_escape(text)), EL_STR("</h2>"));
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<h2 "), attrs), EL_STR(">")), children), EL_STR("</h2>"));
return 0;
}
el_val_t el_h3(el_val_t attrs, el_val_t text) {
el_val_t el_h3(el_val_t attrs, el_val_t children) {
if (str_eq(attrs, EL_STR(""))) {
return el_str_concat(el_str_concat(EL_STR("<h3>"), el_escape(text)), EL_STR("</h3>"));
return el_str_concat(el_str_concat(EL_STR("<h3>"), children), EL_STR("</h3>"));
}
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<h3 "), attrs), EL_STR(">")), el_escape(text)), EL_STR("</h3>"));
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<h3 "), attrs), EL_STR(">")), children), EL_STR("</h3>"));
return 0;
}
el_val_t el_h4(el_val_t attrs, el_val_t text) {
el_val_t el_h4(el_val_t attrs, el_val_t children) {
if (str_eq(attrs, EL_STR(""))) {
return el_str_concat(el_str_concat(EL_STR("<h4>"), el_escape(text)), EL_STR("</h4>"));
return el_str_concat(el_str_concat(EL_STR("<h4>"), children), EL_STR("</h4>"));
}
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<h4 "), attrs), EL_STR(">")), el_escape(text)), EL_STR("</h4>"));
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<h4 "), attrs), EL_STR(">")), children), EL_STR("</h4>"));
return 0;
}
el_val_t el_button(el_val_t attrs, el_val_t label) {
el_val_t el_button(el_val_t attrs, el_val_t children) {
if (str_eq(attrs, EL_STR(""))) {
return el_str_concat(el_str_concat(EL_STR("<button type=\"button\">"), el_escape(label)), EL_STR("</button>"));
return el_str_concat(el_str_concat(EL_STR("<button type=\"button\">"), children), EL_STR("</button>"));
}
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<button type=\"button\" "), attrs), EL_STR(">")), el_escape(label)), EL_STR("</button>"));
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<button type=\"button\" "), attrs), EL_STR(">")), children), EL_STR("</button>"));
return 0;
}
+1 -1
View File
@@ -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("", "Plugin marketplace access (coming soon)")
+ el_li("", "3 marketplace plugins included")
+ el_li("", "Core built-in capabilities")
+ el_li("", "2 devices included")
} else {
+1 -1
View File
@@ -77,7 +77,7 @@ fn marketplace_categories() -> String {
el_div(
"class=\"marketplace-categories reveal\" style=\"transition-delay:320ms\"",
marketplace_tags_block("Connectors (rolling out)", connectors) +
marketplace_tags_block("Connectors - day one", connectors) +
marketplace_tags_block("Following launch", following) +
el_div(
"",
+1 -1
View File
@@ -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("", "Plugin marketplace access (coming soon)")) +
el_li("", el_span("class=\"dash\"", "-") + el_span("", "3 marketplace plugins included")) +
el_li("", el_span("class=\"dash\"", "-") + el_span("", "Core built-in capabilities"))
}
+13 -10
View File
@@ -47,16 +47,16 @@ fn safety_cards() -> String {
el_p("style=\"font-family:var(--body);font-size:0.75rem;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;color:var(--navy);margin-bottom:0.75rem\"", "Hard Bell") +
el_p("style=\"font-family:var(--body);font-weight:400;font-size:0.9375rem;color:var(--t1);margin-bottom:0.5rem\"", "A trusted contact the threat can&#39;t intercept") +
el_p("style=\"font-family:var(--body);font-weight:300;font-size:0.875rem;color:var(--t2);line-height:1.7\"",
"Any user can designate a Hard Bell contact - a friend, a relative, a colleague - set up in a calm moment, independent of any shared account. It&#39;s stored on your device only, never uploaded to our servers. It works the same way for everyone: adult, teen, or child."
"Any user can designate a Hard Bell contact - a friend, a relative, a colleague - set up in a calm moment, independent of any shared account. When a signal warrants it, that contact is reached directly. It works the same way for everyone: adult, teen, or child."
)
)
let card2: String = el_div(
"class=\"reveal card-dark\" style=\"transition-delay:150ms;" + card_style + "\"",
el_p("style=\"font-family:var(--body);font-size:0.75rem;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;color:var(--navy);margin-bottom:0.75rem\"", "Crisis resources") +
el_p("style=\"font-family:var(--body);font-weight:400;font-size:0.9375rem;color:var(--t1);margin-bottom:0.5rem\"", "The help surfaces first - not a notification") +
el_p("style=\"font-family:var(--body);font-size:0.75rem;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;color:var(--navy);margin-bottom:0.75rem\"", "Emergency routing") +
el_p("style=\"font-family:var(--body);font-weight:400;font-size:0.9375rem;color:var(--t1);margin-bottom:0.5rem\"", "Emergency services first - not notification") +
el_p("style=\"font-family:var(--body);font-weight:300;font-size:0.875rem;color:var(--t2);line-height:1.7\"",
"When Neuron detects a physical-danger or crisis signal, it surfaces crisis-line and emergency resources to you directly - 988, the domestic-violence hotline, Crisis Text Line. It evaluates the content of the signal, not the account type. No one in your contact list can disable or redirect this."
"Physical danger and crisis signals route to emergency services and crisis lines. Neuron evaluates the content of the signal, not the account type. No one in your contact list can disable or redirect this path."
)
)
@@ -74,10 +74,10 @@ fn safety_cards() -> String {
let card4: String = el_div(
"class=\"reveal card-dark\" style=\"transition-delay:250ms;" + card_style + "\"",
el_p("style=\"font-family:var(--body);font-size:0.75rem;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;color:var(--navy);margin-bottom:0.75rem\"", "On your device") +
el_p("style=\"font-family:var(--body);font-weight:400;font-size:0.9375rem;color:var(--t1);margin-bottom:0.5rem\"", "Detection never leaves the machine") +
el_p("style=\"font-family:var(--body);font-size:0.75rem;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;color:var(--navy);margin-bottom:0.75rem\"", "Mandatory reporting") +
el_p("style=\"font-family:var(--body);font-weight:400;font-size:0.9375rem;color:var(--t1);margin-bottom:0.5rem\"", "If real harm is intended, authorities may be contacted") +
el_p("style=\"font-family:var(--body);font-weight:300;font-size:0.875rem;color:var(--t2);line-height:1.7\"",
"Crisis detection runs locally, on device, with zero added latency. Triggers are logged on your machine only and never transmitted. Neuron, LLC never sees the signal, the conversation, or that a bell fired."
"If Neuron detects credible, specific indicators of intended harm - to the user or to someone else - emergency services or relevant authorities may be contacted. Every user agrees to this at account creation. You cannot opt out."
)
)
@@ -90,7 +90,7 @@ fn safety_statements() -> String {
let people_first: String = el_div(
"class=\"reveal card-dark\" style=\"padding:2rem 2.5rem;border-left:3px solid var(--navy);margin-bottom:1.5rem\"",
el_p("style=\"font-family:var(--body);font-weight:300;font-size:0.9375rem;color:var(--t2);line-height:1.8;margin-bottom:1.25rem\"",
"This holds for anyone, not just children. When something sounds like serious self-harm or danger, Neuron stops and leads with help - it acknowledges what it heard, tells you it&#39;s concerned, and puts crisis resources in front of you instead of just answering the surface question."
"This applies to anyone planning mass harm, a shooting, or serious self-harm. Not just child protection - anyone. The same logic holds: a misunderstanding can be resolved. A person&#39;s life can&#39;t be given back to them."
) +
el_p("style=\"font-family:var(--body);font-weight:700;font-size:1.125rem;color:var(--navy);line-height:1.4\"",
"People first, always."
@@ -99,7 +99,7 @@ fn safety_statements() -> String {
let stat_card1: String = el_div(
"class=\"reveal card-dark\" style=\"padding:2rem 2.5rem;border-left:3px solid rgba(0,82,160,.30)\"",
el_p("style=\"font-family:var(--body);font-size:0.75rem;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;color:var(--navy);margin-bottom:0.75rem\"", "Why we never point you at your own contacts") +
el_p("style=\"font-family:var(--body);font-size:0.75rem;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;color:var(--navy);margin-bottom:0.75rem\"", "Why the routing works this way") +
el_p("style=\"font-family:var(--body);font-weight:300;font-size:0.9375rem;color:var(--t2);line-height:1.8;margin-bottom:0.75rem\"",
"According to federal child maltreatment data (HHS, Child Maltreatment 2023), approximately 89% of child abuse victims are maltreated by a parent or caregiver. The emergency contact on file is not always a safe contact."
) +
@@ -117,7 +117,7 @@ fn safety_statements() -> String {
"I recognize I will lose business over this. That doesn&#39;t matter to me."
) +
el_p("style=\"font-family:var(--body);font-weight:300;font-size:0.9375rem;color:var(--t2);line-height:1.8\"",
"This is still local. Neuron, LLC does not see your conversations. When a safety signal fires, everything happens on your device - the help is surfaced to you locally. Our servers are never in the loop. We cannot be. That&#39;s the architecture."
"This is still local. Neuron, LLC does not see your conversations. When a safety signal fires, the contact is made by your local instance - not by our servers. We are not in the loop. We cannot be. That&#39;s the architecture."
)
)
@@ -133,6 +133,9 @@ fn safety_statements() -> String {
) +
el_p("style=\"font-family:var(--body);font-weight:300;font-size:0.9375rem;color:var(--t2);line-height:1.8;margin-bottom:1rem\"",
"If you don&#39;t have someone to designate, you can use 988 - the Suicide &amp; Crisis Lifeline - as your Hard Bell contact. The system will accept it. The point is that no one goes in without a line out."
) +
el_p("style=\"font-family:var(--body);font-weight:300;font-size:0.9375rem;color:var(--t2);line-height:1.8\"",
"I&#39;m also establishing a Neuron crisis line - free, 24/7, staffed. Because I recognize that some people don&#39;t have anyone. The technology shouldn&#39;t make that worse. It should be the thing that catches you when nothing else does."
)
)