Streaming resolve scripts arrive after boot, so any extra component
defs sent as <script type="text/sx"> tags weren't being loaded.
Fix in the spec (boot.sx): call (process-sx-scripts nil) at the
start of resolve-suspense so late-arriving component defs are
available in componentEnv before rendering.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolved SX content may reference components not in the initial shell
scan (e.g. ~cart-mini from IO-generated headers). Diff the needed
components against what the shell already sent and prepend any extra
defcomps as a <script type="text/sx"> block before the resolve script.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The streaming page's component scan only covered the suspense
placeholders, missing transitive deps from layout headers (e.g.
~cart-mini, ~auth-menu). Add layout.component_names to Layout class
and include them plus page content_expr in the scan source.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The spec's eval-list calls render-expr for HTML tags/components in eval
position but returned a plain string. When that string was later passed
through _arender (e.g. as a component keyword arg), it got HTML-escaped.
Fix in eval.sx: wrap render-expr result in make-raw-html so the value
carries the raw-html type through any evaluator boundary. Also add
is_render_expr check in async_eval_ref.py as belt-and-suspenders for
the same issue in the async wrapper.
This fixes the streaming demo where suspense placeholder divs were
displayed as escaped text instead of real DOM elements.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The streaming shell now uses render_to_html so [data-suspense] elements
are real DOM elements immediately when the browser parses the HTML.
Previously the shell used SX wire format in a <script data-mount> tag,
requiring sx-browser.js to boot and render before suspense elements
existed — creating a race condition where resolution scripts fired
before the elements were in the DOM.
Now: server renders HTML with suspense placeholders → browser has real
DOM elements → resolution scripts find and replace them reliably.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Async generator bodies don't execute until __anext__(), by which time
the request context is gone. Restructure execute_page_streaming as a
regular async function that does all context-dependent work (g, request,
current_app access, layout resolution, task creation) while the context
is live, then returns an inner async generator that only yields strings
and awaits pre-created tasks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
stream_with_context decorates a generator function, not a generator
instance. Wrap execute_page_streaming in a decorated inner function.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Quart's ASGI layer doesn't propagate request/app context into async
generator iterations. Wrap execute_page_streaming with stream_with_context
so current_app and request remain accessible after each yield.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
boundary.sx now contains only generic web-platform I/O primitives that
any SX host would provide (current-user, request-arg, url-for, etc.).
Moved to boundary-app.sx (deployment-specific):
- Inter-service: frag, query, action, service
- Framework: htmx-request?, g, jinja-global
- Domain: nav-tree, get-children, relations-from
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Client-side routing was only swapping #main-panel content without
updating OOB headers (nav rows, sub-rows). Now each page entry in the
registry includes a layout identity (e.g. "sx-section:Testing") and
try-client-route falls through to server when layout changes, so OOB
header updates are applied correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Complete badge with live demo link to Phase 6 section
- Replace Verification with Demonstration + What to verify sections
- Update Files list: boot.sx spec, bootstrap_js.py, demo files
- Add streaming/suspense and client IO to Current State summary
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server streams HTML shell with ~suspense placeholders immediately,
then sends resolution <script> chunks as async IO completes. Browser
renders loading skeletons instantly, replacing them with real content
as data arrives via __sxResolve().
- defpage :stream true opts pages into streaming response
- ~suspense component renders fallback with data-suspense attr
- resolve-suspense in boot.sx (spec) + bootstrapped to sx-browser.js
- __sxPending queue handles resolution before sx-browser.js loads
- execute_page_streaming() async generator with concurrent IO tasks
- Streaming demo page at /isomorphism/streaming with 1.5s simulated delay
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The SX parser counts \n inside string literals as line breaks, so the
parser's line numbers differ from file line numbers. The naive paren
counter was wrong — the original 8 closing parens was correct.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 5 was solved by IO proxy registration + async DOM renderer +
JavaScript Promises — no continuations needed on the client side.
Continuations remain a prerequisite for Phase 6 (server-side streaming).
Updated plan status: Phases 1-5 complete. Phase 4 moved from Partial.
Renumbered: streaming/suspense is now Phase 6, full iso is Phase 7.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pipeline definitions as .sx files evaluated by a minimal Python runner.
CI primitives (shell-run, docker-build, git-diff-files) are boundary-declared
IO, only available to the runner. Steps are defcomp components composable
by nesting. Fixes pre-existing unclosed parens in isomorphic roadmap section.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
read-spec-file is a server-only page helper. When the client router
tried to evaluate :content, it couldn't find the function. Move all
file reads into the :data expression (evaluated server-side) so
:content only references data bindings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New /testing/ section with 6 pages: overview (all specs), evaluator,
parser, router, renderer, and runners. Each page runs tests server-side
(Python) and offers a browser "Run tests" button (JS). Modular browser
runner (sxRunModularTests) loads framework + per-spec sources from DOM.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Systematic examination of XML, JSON, YAML, JSX, Tcl, Rebol, and Forth
against the six roles SX requires (markup, language, wire format, data
notation, spec language, metaprogramming). Comparison table across five
properties. Every candidate either fails requirements or converges
toward s-expressions under a different name.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>