Commit Graph

348 Commits

Author SHA1 Message Date
ccf9a155ad Merge branch 'worktree-iso-phase-4' into macros 2026-03-07 18:18:32 +00:00
fa70c5f297 Fix streaming demo: preserve app context across async generator yields
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>
2026-03-07 18:18:28 +00:00
3574f7e163 Restructure boundary specs: move app-specific I/O out of language contract
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m42s
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>
2026-03-07 18:04:53 +00:00
6312eb66a2 Merge branch 'worktree-iso-phase-4' into macros
# Conflicts:
#	shared/static/scripts/sx-browser.js
2026-03-07 18:01:56 +00:00
917a487195 Add deps and engine test specs, bootstrap engine to Python
New test specs (test-deps.sx: 33 tests, test-engine.sx: 37 tests) covering
component dependency analysis and engine pure functions. All 6 spec modules
now have formal SX tests: eval (81), parser (39), router (18), render (23),
deps (33), engine (37) = 231 total.

- Add engine as spec module in bootstrap_py.py (alongside deps)
- Add primitive aliases (trim, replace, parse_int, upper) for engine functions
- Fix parse-int to match JS parseInt semantics (strip trailing non-digits)
- Regenerate sx_ref.py with --spec-modules deps,engine
- Update all three test runners (run.js, run.py, sx-test-runner.js)
- Add Dependencies and Engine nav items and testing page entries
- Wire deps-source/engine-source through testing overview UI

Node.js: 231/231 pass. Python: 226/231 (5 pre-existing parser/router gaps).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:01:33 +00:00
605aafa2eb Fix client routing: fall through to server on layout/section change
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>
2026-03-07 17:46:01 +00:00
a05d642461 Phase 6: Streaming & Suspense — chunked HTML with suspense resolution
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>
2026-03-07 17:34:10 +00:00
aab1f3e966 Modular test architecture: per-module test specs for SX
Split monolithic test.sx into composable test specs:
- test-framework.sx: deftest/defsuite macros + assertion helpers
- test-eval.sx: core evaluator + primitives (81 tests)
- test-parser.sx: parser + serializer + round-trips (39 tests)
- test-router.sx: route matching from router.sx (18 tests)
- test-render.sx: HTML adapter rendering (23 tests)

Runners auto-discover specs and test whatever bootstrapped code
is available. Usage: `run.js eval parser router` or just `run.js`.
Legacy mode (`--legacy`) still runs monolithic test.sx.

Router tests use bootstrapped functions (sx_ref.py / sx-browser.js)
because the hand-written evaluator's flat-dict env model doesn't
support set! mutation across lambda closure boundaries.

JS: 161/161. Python: 159/161 (2 parser escape bugs found).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 12:17:13 +00:00
3a12368c9d Merge branch 'worktree-iso-phase-4' into macros 2026-03-07 11:03:48 +00:00
bec881acb3 Fix asset-url: use Jinja global instead of nonexistent urls.asset_url
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>
2026-03-07 11:03:46 +00:00
e89c496dc8 Merge branch 'worktree-iso-phase-4' into macros 2026-03-07 11:00:51 +00:00
e9d86d628b Make test.sx self-executing: evaluators run it directly, no codegen
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>
2026-03-07 10:50:28 +00:00
754e7557f5 Add self-hosting SX test spec: 81 tests bootstrap to Python + JS
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>
2026-03-07 10:41:53 +00:00
f674a5edcc Merge branch 'worktree-iso-phase-4' into macros 2026-03-07 10:06:00 +00:00
e09bc3b601 Fix test_sx_js: temp file for large scripts, globalThis for Node file mode
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>
2026-03-07 10:06:00 +00:00
43f2547de8 Merge branch 'worktree-iso-phase-4' into macros 2026-03-07 10:01:35 +00:00
8366088ee1 Add Phase 5 unit tests: IO proxy, io-deps registry, SxExpr roundtrip
22 tests covering:
- io-deps page registry field (pure, IO, transitive, serialization)
- Dynamic IO allowlist construction from component io_refs
- SxExpr serialize→parse roundtrip (unquoted, fragments, nil, in-dict)
- IO proxy arg parsing (GET query string vs POST JSON body)
- Orchestration routing logic (io-deps truthiness, parsed entries)
- IO cache key determinism

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 10:01:35 +00:00
fd20811afa Merge branch 'worktree-iso-phase-4' into macros 2026-03-07 09:51:51 +00:00
84ea5d4c16 IO proxy: client-side cache with 5min TTL, server Cache-Control
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>
2026-03-07 09:51:51 +00:00
51990d9445 Merge branch 'worktree-iso-phase-4' into macros 2026-03-07 09:50:23 +00:00
0d6b959045 Mark IO proxy endpoint as CSRF-exempt (read-only, no state mutation)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 09:50:23 +00:00
847d5d1f31 Merge branch 'worktree-iso-phase-4' into macros 2026-03-07 09:40:24 +00:00
ff2ef29d8a Fix async map: use Lambda.params/body/closure (not _params/_body/_closure)
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>
2026-03-07 09:40:23 +00:00
ab27491157 Merge branch 'worktree-iso-phase-4' into macros 2026-03-07 09:23:24 +00:00
aa67b036c7 IO proxy: POST for long payloads, network error resilience
- 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>
2026-03-07 09:23:20 +00:00
9ac90a787d Merge branch 'worktree-iso-phase-4' into macros 2026-03-07 09:14:07 +00:00
cb0990feb3 Dynamic IO proxy: derive proxied primitives from component io_refs
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>
2026-03-07 09:13:53 +00:00
69ced865db Merge branch 'worktree-iso-phase-1' into macros
# Conflicts:
#	shared/sx/helpers.py
#	shared/sx/pages.py
#	sx/sx/nav-data.sx
#	sx/sx/plans.sx
#	sx/sxc/pages/docs.sx
2026-03-07 08:38:32 +00:00
6215d3573b Send content expression component deps in SX responses for client routing
When a page has a content expression but no data dependency, compute its
transitive component deps and pass them as extra_component_names to
sx_response(). This ensures the client has all component definitions
needed for future client-side route rendering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 08:35:20 +00:00
79fa1411dc Phase 5: async IO rendering — components call IO primitives client-side
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
04ff03f5d4 Live-read all DOM attributes: forms and preloads too
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m49s
bindBoostForm re-reads method/action at submit time.
bind-preload-for re-reads verb-info and headers at preload time.
No closed-over stale values anywhere in the event binding system.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 02:11:09 +00:00
b85a46bb62 Re-read element attributes at click time, not from closed-over bind values
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m39s
All click handlers (bind-event, bindBoostLink, bindClientRouteClick)
now re-read href/verb-info from the DOM element when the click fires,
instead of using values captured at bind time. This ensures correct
behavior when DOM is replaced or attributes are morphed after binding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 02:06:21 +00:00
09d06a4c87 Filter data page deps by IO purity: only bundle pure component trees
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m43s
Pages whose component trees reference IO primitives (e.g. highlight)
cannot render client-side, so exclude their deps from the client bundle.
This prevents "Undefined symbol: highlight" errors on pages like
bundle-analyzer while still allowing pure data pages like data-test
to render client-side with caching.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:48:47 +00:00
6655f638b9 Optimize evaluator hot path: prototype-chain envs, imperative kwarg parsing
Three key optimizations to the JS evaluator platform layer:

1. envMerge uses Object.create() instead of copying all keys — O(own) vs O(all)
2. renderDomComponent/renderDomElement override: imperative kwarg/attr
   parsing replaces reduce+assoc pattern (no per-arg dict allocation)
3. callComponent/parseKeywordArgs override: same imperative pattern
   for the eval path (not just DOM rendering)

Wire format and spec semantics unchanged — these are host-level
performance overrides in the platform JS.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:42:09 +00:00
2c56d3e14b Include all :data page component deps in every page's client bundle
Per-page bundling now unions deps from all :data pages in the service,
so navigating between data pages uses client-side rendering + cache
instead of expensive server fetch + SX parse.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:26:39 +00:00
fa295acfe3 Remove debug logs from client routing, Phase 4 confirmed working
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:23:32 +00:00
28ee441d9a Debug: log fallback path when client route fails
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:20:58 +00:00
1387d97c82 Clean up debug logs from try-client-route, keep deps check
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:16:24 +00:00
b90cc59029 Check component deps before attempting client-side route render
Pages whose components aren't loaded client-side now fall through
to server fetch instead of silently failing in the async callback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:13:09 +00:00
c15dbc3242 Debug: log has-data type and cache status in try-client-route
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:03:48 +00:00
ece2aa225d Fix popstate and client routing when no [sx-boost] container exists
handle-popstate falls back to #main-panel when no [sx-boost] element
is found, fixing back button for apps using explicit sx-target attrs.
bindClientRouteClick also checks sx-target on the link itself.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:53:08 +00:00
ac1dc34dad Fix: pass target selector to tryClientRoute from link clicks
bindClientRouteClick was calling tryClientRoute(pathname) without the
target-sel argument. This caused resolve-route-target to return nil,
so client routing ALWAYS fell back to server fetch on link clicks.
Now finds the sx-boost ancestor and passes its target selector.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:46:25 +00:00
f36583b620 Fix: register append!/dict-set! in PRIMITIVES after it is defined
The registrations were in the platform eval block which emits before
var PRIMITIVES = {}. Moved to core.list and core.dict primitive sections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:33:49 +00:00
6772f1141f Register append! and dict-set! as proper primitives
Previously these mutating operations were internal helpers in the JS
bootstrapper but not declared in primitives.sx or registered in the
Python evaluator. Now properly specced and available in both hosts.

Removes mock injections from cache tests — they use real primitives.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:21:17 +00:00
60b58fdff7 Add cache unit tests (10) and update data-test demo for TTL
- 10 new tests: cache key generation, set/get, TTL expiry, overwrite,
  key independence, complex nested data
- Update data-test.sx with cache verification instructions:
  navigate away+back within 30s → client+cache, after 30s → new fetch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:18:11 +00:00
d3617ab7f3 Phase 4 complete: client data cache + plan update
- Add page data cache in orchestration.sx (30s TTL, keyed by page-name+params)
- Cache hit path: sx:route client+cache (instant render, no fetch)
- Cache miss path: sx:route client+data (fetch, cache, render)
- Fix HTMX response dep computation to include :data pages
- Update isomorphic-sx-plan.md: Phases 1-4 marked done with details,
  reorder remaining phases (continuations→Phase 5, suspense→Phase 6,
  optimistic updates→Phase 7)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:06:22 +00:00
732923a7ef Fix: auto-include router spec module when boot adapter is present
boot.sx uses parse-route-pattern from router.sx, but router was only
included as an opt-in spec module. Now auto-included when boot is in
the adapter set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:53:55 +00:00
b1f9e41027 Add unit tests for Phase 4 page data pipeline (20 tests)
Tests cover: SX wire format roundtrip for data dicts (11 tests),
kebab-case key conversion (4 tests), component dep computation for
:data pages (2 tests), and full pipeline simulation — serialize on
server, parse on client, merge into env, eval content (3 tests).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:49:08 +00:00
a657d0831c Phase 4: Client-side rendering of :data pages via abstract resolve-page-data
Spec layer (orchestration.sx):
- try-client-route now handles :data pages instead of falling back to server
- New abstract primitive resolve-page-data(name, params, callback) — platform
  decides transport (HTTP, IPC, cache, etc)
- Extracted swap-rendered-content and resolve-route-target helpers

Platform layer (bootstrap_js.py):
- resolvePageData() browser implementation: fetches /sx/data/<name>, parses
  SX response, calls callback. Other hosts provide their own transport.

Server layer (pages.py):
- evaluate_page_data() evaluates :data expr, serializes result as SX
- auto_mount_page_data() mounts /sx/data/ endpoint with per-page auth
- _build_pages_sx now computes component deps for all pages (not just pure)

Test page at /isomorphism/data-test exercises the full pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:46:30 +00:00
eee2954559 Update reference docs: fix event names, add demos, document sx-boost target
- Remove sx:afterSettle (not dispatched), rename sx:sendError → sx:requestError
- Add sx:clientRoute event (Phase 3 client-side routing)
- Add working demos for all 10 events (afterRequest, afterSwap, requestError,
  clientRoute, sseOpen, sseMessage, sseError were missing demos)
- Update sx-boost docs: configurable target selector, client routing behavior
- Remove app-specific nav logic from orchestration.sx, use sx:clientRoute event
- Pass page content deps to sx_response for component loading after server fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:38 +00:00