The web's HTML/CSS/JS split separates the framework's concerns,
not the application domain's. Real separation of concerns is
domain-specific and cannot be prescribed by a platform.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pushing to main triggers a production deploy — make this explicit
in the deployment section so it's never done accidentally.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Python evaluator runs test.sx at page load, results shown alongside
the browser runner. Both hosts prove the same 81 tests from the same
spec file — server on render, client on click.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SX testing SX is the strange loop made concrete — the language proves
its own correctness using its own macros. Links to /specs/testing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The IO handler and bridge both imported asset_url from
shared.infrastructure.urls, but it doesn't exist there — it's a Jinja
global defined in jinja_setup.py. Use current_app.jinja_env.globals.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sx-browser.js evaluates test.sx directly in the browser — click
"Run 81 tests" to see SX test itself. Uses the same Sx global that
rendered the page.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
test.sx now defines deftest/defsuite as macros. Any host that provides
5 platform functions (try-call, report-pass, report-fail, push-suite,
pop-suite) can evaluate the file directly — no bootstrap compilation
step needed for JS.
- Added defmacro for deftest (wraps body in thunk, catches via try-call)
- Added defmacro for defsuite (push/pop suite context stack)
- Created run.js: sx-browser.js evaluates test.sx directly (81/81 pass)
- Created run.py: Python evaluator evaluates test.sx directly (81/81 pass)
- Deleted bootstrap_test_js.py and generated test_sx_spec.js
- Updated testing docs page to reflect self-executing architecture
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test framework is written in SX and tests SX — the language proves
its own correctness. test.sx defines assertion helpers (assert-equal,
assert-true, assert-type, etc.) and 15 test suites covering literals,
arithmetic, comparison, strings, lists, dicts, predicates, special forms,
lambdas, higher-order forms, components, macros, threading, truthiness,
and edge cases.
Two bootstrap compilers emit native tests from the same spec:
- bootstrap_test.py → pytest (81/81 pass)
- bootstrap_test_js.py → Node.js TAP using sx-browser.js (81/81 pass)
Also adds missing primitives to spec and Python evaluator: boolean?,
string-length, substring, string-contains?, upcase, downcase, reverse,
flatten, has-key?. Fixes number? to exclude booleans, append to
concatenate lists.
Includes testing docs page in SX app at /specs/testing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sx-browser.js grew past OS arg length limit for node -e. Write to
temp file instead. Also fix Sx global scope: Node file mode sets
`this` to module.exports, not globalThis, so the IIFE wrapper needs
.call(globalThis) to make Sx accessible to sx-test.js.
855 passed (2 pre-existing empty .sx file failures).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Client caches IO results by (name + args) in memory. In-flight
promises are cached too (dedup concurrent calls for same args).
Server adds Cache-Control: public, max-age=300 for HTTP caching.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Lambda constructor stores properties without underscore prefix,
but asyncRenderMap/asyncRenderMapIndexed accessed them with underscores.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Switch to POST with JSON body when query string exceeds 1500 chars
(highlight calls with large component sources hit URL length limits)
- Include CSRF token header on POST requests
- Add .catch() on fetch to gracefully handle network errors (return NIL)
- Upgrade async eval miss logs from logInfo to logWarn for visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hardcoded IO primitive lists on both client and server with
data-driven registration. Page registry entries carry :io-deps (list
of IO primitive names) instead of :has-io boolean. Client registers
proxied IO on demand per page via registerIoDeps(). Server builds
allowlist from component analysis.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
highlight returns SxExpr (SX source with colored spans), not raw HTML.
Must render via evaluator (~doc-code :code), not (raw! ...). Also
replace JavaScript example with SX (no JS highlighter exists).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire async rendering into client-side routing: pages whose component
trees reference IO primitives (highlight, current-user, etc.) now
render client-side via Promise-aware asyncRenderToDom. IO calls proxy
through /sx/io/<name> endpoint, which falls back to page helpers.
- Add has-io flag to page registry entries (helpers.py)
- Remove IO purity filter — include IO-dependent components in bundles
- Extend try-client-route with 4 paths: pure, data, IO, data+IO
- Convert tryAsyncEvalContent to callback style, add platform mapping
- IO proxy falls back to page helpers (highlight works via proxy)
- Demo page: /isomorphism/async-io with inline highlight calls
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 08:12:42 +00:00
25 changed files with 4061 additions and 278 deletions
@@ -5,6 +5,7 @@ Cooperative web platform: federated content, commerce, events, and media process
## Deployment
## Deployment
- **Do NOT push** until explicitly told to. Pushes reload code to dev automatically.
- **Do NOT push** until explicitly told to. Pushes reload code to dev automatically.
- **NEVER push to `main`** — pushing to main triggers a **PRODUCTION deploy**. Only push to main when the user explicitly requests a production deploy. Work on the `macros` branch by default; merge to main only with explicit permission.
return(isSxTruthy(sxOr(isNil(contentSrc),isEmpty(contentSrc)))?(logWarn((String("sx:route no content for ")+String(pathname))),false):(function(){
return(isSxTruthy(sxOr(isNil(contentSrc),isEmpty(contentSrc)))?(logWarn((String("sx:route no content for ")+String(pathname))),false):(function(){
vartarget=resolveRouteTarget(targetSel);
vartarget=resolveRouteTarget(targetSel);
return(isSxTruthy(isNil(target))?(logWarn((String("sx:route target not found: ")+String(targetSel))),false):(isSxTruthy(!isSxTruthy(depsSatisfied_p(match)))?(logInfo((String("sx:route deps miss for ")+String(pageName))),false):(isSxTruthy(get(match,"has-data"))?(function(){
return(isSxTruthy(isNil(target))?(logWarn((String("sx:route target not found: ")+String(targetSel))),false):(isSxTruthy(!isSxTruthy(depsSatisfied_p(match)))?(logInfo((String("sx:route deps miss for ")+String(pageName))),false):(function(){
return(isSxTruthy(hasIo)?tryAsyncEvalContent(contentSrc,env,function(rendered){return(isSxTruthy(isNil(rendered))?logWarn((String("sx:route data+async eval failed for ")+String(pathname))):swapRenderedContent(target,rendered,pathname));}):(function(){
varrendered=tryEvalContent(contentSrc,env);
varrendered=tryEvalContent(contentSrc,env);
return(isSxTruthy(isNil(rendered))?logWarn((String("sx:route data eval failed for ")+String(pathname))):swapRenderedContent(target,rendered,pathname));
return(isSxTruthy(isNil(rendered))?logWarn((String("sx:route data eval failed for ")+String(pathname))):swapRenderedContent(target,rendered,pathname));
})());
})();}),true));
})();}),true));
})():(function(){
})():(isSxTruthy(hasIo)?(logInfo((String("sx:route client+async ")+String(pathname))),tryAsyncEvalContent(contentSrc,merge(closure,params),function(rendered){return(isSxTruthy(isNil(rendered))?logWarn((String("sx:route async eval failed for ")+String(pathname))):swapRenderedContent(target,rendered,pathname));}),true):(function(){
varenv=merge(closure,params);
varenv=merge(closure,params);
varrendered=tryEvalContent(contentSrc,env);
varrendered=tryEvalContent(contentSrc,env);
return(isSxTruthy(isNil(rendered))?(logInfo((String("sx:route server (eval failed) ")+String(pathname))),false):(swapRenderedContent(target,rendered,pathname),true));
return(isSxTruthy(isNil(rendered))?(logInfo((String("sx:route server (eval failed) ")+String(pathname))),false):(swapRenderedContent(target,rendered,pathname),true));