Page helpers demo: defisland, map-in-children fix, _eval_slot ref evaluator

- Add page-helpers-demo page with defisland ~demo-client-runner (pure SX,
  zero JS files) showing spec functions running on both server and client
- Fix _aser_component children serialization: flatten list results from map
  instead of serialize(list) which wraps in parens creating ((div ...) ...)
  that re-parses as invalid function call. Fixed in adapter-async.sx spec
  and async_eval_ref.py
- Switch _eval_slot to use async_eval_ref.py when SX_USE_REF=1 (was
  hardcoded to async_eval.py)
- Add Island type support to async_eval_ref.py: import, SSR rendering,
  aser dispatch, thread-first, defisland in _ASER_FORMS
- Add server affinity check: components with :affinity :server expand
  even when _expand_components is False
- Add diagnostic _aser_stack context to EvalError messages
- New spec files: adapter-async.sx, page-helpers.sx, platform_js.py
- Bootstrappers: page-helpers module support, performance.now() timing
- 0-arity lambda event handler fix in adapter-dom.sx

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 14:30:12 +00:00
parent 29c90a625b
commit c95e19dcf2
16 changed files with 5584 additions and 781 deletions

View File

@@ -2342,452 +2342,147 @@ def env_components(env):
return filter(lambda k: (lambda v: (is_component(v) if sx_truthy(is_component(v)) else is_macro(v)))(env_get(env, k)), keys(env))
# === Transpiled from engine (fetch/swap/trigger pure logic) ===
# === Transpiled from page-helpers (pure data transformation helpers) ===
# ENGINE_VERBS
ENGINE_VERBS = ['get', 'post', 'put', 'delete', 'patch']
# special-form-category-map
special_form_category_map = {'if': 'Control Flow', 'when': 'Control Flow', 'cond': 'Control Flow', 'case': 'Control Flow', 'and': 'Control Flow', 'or': 'Control Flow', 'let': 'Binding', 'let*': 'Binding', 'letrec': 'Binding', 'define': 'Binding', 'set!': 'Binding', 'lambda': 'Functions & Components', 'fn': 'Functions & Components', 'defcomp': 'Functions & Components', 'defmacro': 'Functions & Components', 'begin': 'Sequencing & Threading', 'do': 'Sequencing & Threading', '->': 'Sequencing & Threading', 'quote': 'Quoting', 'quasiquote': 'Quoting', 'reset': 'Continuations', 'shift': 'Continuations', 'dynamic-wind': 'Guards', 'map': 'Higher-Order Forms', 'map-indexed': 'Higher-Order Forms', 'filter': 'Higher-Order Forms', 'reduce': 'Higher-Order Forms', 'some': 'Higher-Order Forms', 'every?': 'Higher-Order Forms', 'for-each': 'Higher-Order Forms', 'defstyle': 'Domain Definitions', 'defhandler': 'Domain Definitions', 'defpage': 'Domain Definitions', 'defquery': 'Domain Definitions', 'defaction': 'Domain Definitions'}
# DEFAULT_SWAP
DEFAULT_SWAP = 'outerHTML'
# extract-define-kwargs
def extract_define_kwargs(expr):
result = {}
items = slice(expr, 2)
n = len(items)
for idx in range(0, n):
if sx_truthy((((idx + 1) < n) if not sx_truthy(((idx + 1) < n)) else (type_of(nth(items, idx)) == 'keyword'))):
key = keyword_name(nth(items, idx))
val = nth(items, (idx + 1))
result[key] = (sx_str('(', join(' ', map(serialize, val)), ')') if sx_truthy((type_of(val) == 'list')) else sx_str(val))
return result
# parse-time
def parse_time(s):
if sx_truthy(is_nil(s)):
return 0
elif sx_truthy(ends_with_p(s, 'ms')):
return parse_int(s, 0)
elif sx_truthy(ends_with_p(s, 's')):
return (parse_int(replace(s, 's', ''), 0) * 1000)
# categorize-special-forms
def categorize_special_forms(parsed_exprs):
categories = {}
for expr in parsed_exprs:
if sx_truthy(((type_of(expr) == 'list') if not sx_truthy((type_of(expr) == 'list')) else ((len(expr) >= 2) if not sx_truthy((len(expr) >= 2)) else ((type_of(first(expr)) == 'symbol') if not sx_truthy((type_of(first(expr)) == 'symbol')) else (symbol_name(first(expr)) == 'define-special-form'))))):
name = nth(expr, 1)
kwargs = extract_define_kwargs(expr)
category = (get(special_form_category_map, name) if sx_truthy(get(special_form_category_map, name)) else 'Other')
if sx_truthy((not sx_truthy(has_key_p(categories, category)))):
categories[category] = []
get(categories, category).append({'name': name, 'syntax': (get(kwargs, 'syntax') if sx_truthy(get(kwargs, 'syntax')) else ''), 'doc': (get(kwargs, 'doc') if sx_truthy(get(kwargs, 'doc')) else ''), 'tail-position': (get(kwargs, 'tail-position') if sx_truthy(get(kwargs, 'tail-position')) else ''), 'example': (get(kwargs, 'example') if sx_truthy(get(kwargs, 'example')) else '')})
return categories
# build-ref-items-with-href
def build_ref_items_with_href(items, base_path, detail_keys, n_fields):
return map(lambda item: ((lambda name: (lambda field2: (lambda field3: {'name': name, 'desc': field2, 'exists': field3, 'href': (sx_str(base_path, name) if sx_truthy((field3 if not sx_truthy(field3) else some(lambda k: (k == name), detail_keys))) else NIL)})(nth(item, 2)))(nth(item, 1)))(nth(item, 0)) if sx_truthy((n_fields == 3)) else (lambda name: (lambda desc: {'name': name, 'desc': desc, 'href': (sx_str(base_path, name) if sx_truthy(some(lambda k: (k == name), detail_keys)) else NIL)})(nth(item, 1)))(nth(item, 0))), items)
# build-reference-data
def build_reference_data(slug, raw_data, detail_keys):
_match = slug
if _match == 'attributes':
return {'req-attrs': build_ref_items_with_href(get(raw_data, 'req-attrs'), '/hypermedia/reference/attributes/', detail_keys, 3), 'beh-attrs': build_ref_items_with_href(get(raw_data, 'beh-attrs'), '/hypermedia/reference/attributes/', detail_keys, 3), 'uniq-attrs': build_ref_items_with_href(get(raw_data, 'uniq-attrs'), '/hypermedia/reference/attributes/', detail_keys, 3)}
elif _match == 'headers':
return {'req-headers': build_ref_items_with_href(get(raw_data, 'req-headers'), '/hypermedia/reference/headers/', detail_keys, 3), 'resp-headers': build_ref_items_with_href(get(raw_data, 'resp-headers'), '/hypermedia/reference/headers/', detail_keys, 3)}
elif _match == 'events':
return {'events-list': build_ref_items_with_href(get(raw_data, 'events-list'), '/hypermedia/reference/events/', detail_keys, 2)}
elif _match == 'js-api':
return {'js-api-list': map(lambda item: {'name': nth(item, 0), 'desc': nth(item, 1)}, get(raw_data, 'js-api-list'))}
else:
return parse_int(s, 0)
return {'req-attrs': build_ref_items_with_href(get(raw_data, 'req-attrs'), '/hypermedia/reference/attributes/', detail_keys, 3), 'beh-attrs': build_ref_items_with_href(get(raw_data, 'beh-attrs'), '/hypermedia/reference/attributes/', detail_keys, 3), 'uniq-attrs': build_ref_items_with_href(get(raw_data, 'uniq-attrs'), '/hypermedia/reference/attributes/', detail_keys, 3)}
# parse-trigger-spec
def parse_trigger_spec(spec):
if sx_truthy(is_nil(spec)):
return NIL
# build-attr-detail
def build_attr_detail(slug, detail):
if sx_truthy(is_nil(detail)):
return {'attr-not-found': True}
else:
raw_parts = split(spec, ',')
return filter(lambda x: (not sx_truthy(is_nil(x))), map(lambda part: (lambda tokens: (NIL if sx_truthy(empty_p(tokens)) else ({'event': 'every', 'modifiers': {'interval': parse_time(nth(tokens, 1))}} if sx_truthy(((first(tokens) == 'every') if not sx_truthy((first(tokens) == 'every')) else (len(tokens) >= 2))) else (lambda mods: _sx_begin(for_each(lambda tok: (_sx_dict_set(mods, 'once', True) if sx_truthy((tok == 'once')) else (_sx_dict_set(mods, 'changed', True) if sx_truthy((tok == 'changed')) else (_sx_dict_set(mods, 'delay', parse_time(slice(tok, 6))) if sx_truthy(starts_with_p(tok, 'delay:')) else (_sx_dict_set(mods, 'from', slice(tok, 5)) if sx_truthy(starts_with_p(tok, 'from:')) else NIL)))), rest(tokens)), {'event': first(tokens), 'modifiers': mods}))({}))))(split(trim(part), ' ')), raw_parts))
return {'attr-not-found': NIL, 'attr-title': slug, 'attr-description': get(detail, 'description'), 'attr-example': get(detail, 'example'), 'attr-handler': get(detail, 'handler'), 'attr-demo': get(detail, 'demo'), 'attr-wire-id': (sx_str('ref-wire-', replace(replace(slug, ':', '-'), '*', 'star')) if sx_truthy(has_key_p(detail, 'handler')) else NIL)}
# default-trigger
def default_trigger(tag_name):
if sx_truthy((tag_name == 'FORM')):
return [{'event': 'submit', 'modifiers': {}}]
elif sx_truthy(((tag_name == 'INPUT') if sx_truthy((tag_name == 'INPUT')) else ((tag_name == 'SELECT') if sx_truthy((tag_name == 'SELECT')) else (tag_name == 'TEXTAREA')))):
return [{'event': 'change', 'modifiers': {}}]
# build-header-detail
def build_header_detail(slug, detail):
if sx_truthy(is_nil(detail)):
return {'header-not-found': True}
else:
return [{'event': 'click', 'modifiers': {}}]
return {'header-not-found': NIL, 'header-title': slug, 'header-direction': get(detail, 'direction'), 'header-description': get(detail, 'description'), 'header-example': get(detail, 'example'), 'header-demo': get(detail, 'demo')}
# get-verb-info
def get_verb_info(el):
return some(lambda verb: (lambda url: ({'method': upper(verb), 'url': url} if sx_truthy(url) else NIL))(dom_get_attr(el, sx_str('sx-', verb))), ENGINE_VERBS)
# build-event-detail
def build_event_detail(slug, detail):
if sx_truthy(is_nil(detail)):
return {'event-not-found': True}
else:
return {'event-not-found': NIL, 'event-title': slug, 'event-description': get(detail, 'description'), 'event-example': get(detail, 'example'), 'event-demo': get(detail, 'demo')}
# build-request-headers
def build_request_headers(el, loaded_components, css_hash):
headers = {'SX-Request': 'true', 'SX-Current-URL': browser_location_href()}
target_sel = dom_get_attr(el, 'sx-target')
if sx_truthy(target_sel):
headers['SX-Target'] = target_sel
if sx_truthy((not sx_truthy(empty_p(loaded_components)))):
headers['SX-Components'] = join(',', loaded_components)
if sx_truthy(css_hash):
headers['SX-Css'] = css_hash
extra_h = dom_get_attr(el, 'sx-headers')
if sx_truthy(extra_h):
parsed = parse_header_value(extra_h)
if sx_truthy(parsed):
for key in keys(parsed):
headers[key] = sx_str(get(parsed, key))
return headers
# build-component-source
def build_component_source(comp_data):
comp_type = get(comp_data, 'type')
name = get(comp_data, 'name')
params = get(comp_data, 'params')
has_children = get(comp_data, 'has-children')
body_sx = get(comp_data, 'body-sx')
affinity = get(comp_data, 'affinity')
if sx_truthy((comp_type == 'not-found')):
return sx_str(';; component ', name, ' not found')
else:
param_strs = ((['&rest', 'children'] if sx_truthy(has_children) else []) if sx_truthy(empty_p(params)) else (append(cons('&key', params), ['&rest', 'children']) if sx_truthy(has_children) else cons('&key', params)))
params_sx = sx_str('(', join(' ', param_strs), ')')
form_name = ('defisland' if sx_truthy((comp_type == 'island')) else 'defcomp')
affinity_str = (sx_str(' :affinity ', affinity) if sx_truthy(((comp_type == 'component') if not sx_truthy((comp_type == 'component')) else ((not sx_truthy(is_nil(affinity))) if not sx_truthy((not sx_truthy(is_nil(affinity)))) else (not sx_truthy((affinity == 'auto')))))) else '')
return sx_str('(', form_name, ' ', name, ' ', params_sx, affinity_str, '\n ', body_sx, ')')
# process-response-headers
def process_response_headers(get_header):
return {'redirect': get_header('SX-Redirect'), 'refresh': get_header('SX-Refresh'), 'trigger': get_header('SX-Trigger'), 'retarget': get_header('SX-Retarget'), 'reswap': get_header('SX-Reswap'), 'location': get_header('SX-Location'), 'replace-url': get_header('SX-Replace-Url'), 'css-hash': get_header('SX-Css-Hash'), 'trigger-swap': get_header('SX-Trigger-After-Swap'), 'trigger-settle': get_header('SX-Trigger-After-Settle'), 'content-type': get_header('Content-Type'), 'cache-invalidate': get_header('SX-Cache-Invalidate'), 'cache-update': get_header('SX-Cache-Update')}
# parse-swap-spec
def parse_swap_spec(raw_swap, global_transitions_p):
# build-bundle-analysis
def build_bundle_analysis(pages_raw, components_raw, total_components, total_macros, pure_count, io_count):
_cells = {}
parts = split((raw_swap if sx_truthy(raw_swap) else DEFAULT_SWAP), ' ')
style = first(parts)
_cells['use_transition'] = global_transitions_p
for p in rest(parts):
if sx_truthy((p == 'transition:true')):
_cells['use_transition'] = True
elif sx_truthy((p == 'transition:false')):
_cells['use_transition'] = False
return {'style': style, 'transition': _cells['use_transition']}
pages_data = []
for page in pages_raw:
needed_names = get(page, 'needed-names')
n = len(needed_names)
pct = (round(((n / total_components) * 100)) if sx_truthy((total_components > 0)) else 0)
savings = (100 - pct)
_cells['pure_in_page'] = 0
_cells['io_in_page'] = 0
page_io_refs = []
comp_details = []
for comp_name in needed_names:
info = get(components_raw, comp_name)
if sx_truthy((not sx_truthy(is_nil(info)))):
if sx_truthy(get(info, 'is-pure')):
_cells['pure_in_page'] = (_cells['pure_in_page'] + 1)
else:
_cells['io_in_page'] = (_cells['io_in_page'] + 1)
for ref in (get(info, 'io-refs') if sx_truthy(get(info, 'io-refs')) else []):
if sx_truthy((not sx_truthy(some(lambda r: (r == ref), page_io_refs)))):
page_io_refs.append(ref)
comp_details.append({'name': comp_name, 'is-pure': get(info, 'is-pure'), 'affinity': get(info, 'affinity'), 'render-target': get(info, 'render-target'), 'io-refs': (get(info, 'io-refs') if sx_truthy(get(info, 'io-refs')) else []), 'deps': (get(info, 'deps') if sx_truthy(get(info, 'deps')) else []), 'source': get(info, 'source')})
pages_data.append({'name': get(page, 'name'), 'path': get(page, 'path'), 'direct': get(page, 'direct'), 'needed': n, 'pct': pct, 'savings': savings, 'io-refs': len(page_io_refs), 'pure-in-page': _cells['pure_in_page'], 'io-in-page': _cells['io_in_page'], 'components': comp_details})
return {'pages': pages_data, 'total-components': total_components, 'total-macros': total_macros, 'pure-count': pure_count, 'io-count': io_count}
# parse-retry-spec
def parse_retry_spec(retry_attr):
if sx_truthy(is_nil(retry_attr)):
return NIL
else:
parts = split(retry_attr, ':')
return {'strategy': first(parts), 'start-ms': parse_int(nth(parts, 1), 1000), 'cap-ms': parse_int(nth(parts, 2), 30000)}
# next-retry-ms
def next_retry_ms(current_ms, cap_ms):
return min((current_ms * 2), cap_ms)
# filter-params
def filter_params(params_spec, all_params):
if sx_truthy(is_nil(params_spec)):
return all_params
elif sx_truthy((params_spec == 'none')):
return []
elif sx_truthy((params_spec == '*')):
return all_params
elif sx_truthy(starts_with_p(params_spec, 'not ')):
excluded = map(trim, split(slice(params_spec, 4), ','))
return filter(lambda p: (not sx_truthy(contains_p(excluded, first(p)))), all_params)
else:
allowed = map(trim, split(params_spec, ','))
return filter(lambda p: contains_p(allowed, first(p)), all_params)
# resolve-target
def resolve_target(el):
sel = dom_get_attr(el, 'sx-target')
if sx_truthy((is_nil(sel) if sx_truthy(is_nil(sel)) else (sel == 'this'))):
return el
elif sx_truthy((sel == 'closest')):
return dom_parent(el)
else:
return dom_query(sel)
# apply-optimistic
def apply_optimistic(el):
directive = dom_get_attr(el, 'sx-optimistic')
if sx_truthy(is_nil(directive)):
return NIL
else:
target = (resolve_target(el) if sx_truthy(resolve_target(el)) else el)
state = {'target': target, 'directive': directive}
(_sx_begin(_sx_dict_set(state, 'opacity', dom_get_style(target, 'opacity')), dom_set_style(target, 'opacity', '0'), dom_set_style(target, 'pointer-events', 'none')) if sx_truthy((directive == 'remove')) else (_sx_begin(_sx_dict_set(state, 'disabled', dom_get_prop(target, 'disabled')), dom_set_prop(target, 'disabled', True)) if sx_truthy((directive == 'disable')) else ((lambda cls: _sx_begin(_sx_dict_set(state, 'add-class', cls), dom_add_class(target, cls)))(slice(directive, 10)) if sx_truthy(starts_with_p(directive, 'add-class:')) else NIL)))
return state
# revert-optimistic
def revert_optimistic(state):
if sx_truthy(state):
target = get(state, 'target')
directive = get(state, 'directive')
if sx_truthy((directive == 'remove')):
dom_set_style(target, 'opacity', (get(state, 'opacity') if sx_truthy(get(state, 'opacity')) else ''))
return dom_set_style(target, 'pointer-events', '')
elif sx_truthy((directive == 'disable')):
return dom_set_prop(target, 'disabled', (get(state, 'disabled') if sx_truthy(get(state, 'disabled')) else False))
elif sx_truthy(get(state, 'add-class')):
return dom_remove_class(target, get(state, 'add-class'))
return NIL
return NIL
# find-oob-swaps
def find_oob_swaps(container):
results = []
for attr in ['sx-swap-oob', 'hx-swap-oob']:
oob_els = dom_query_all(container, sx_str('[', attr, ']'))
for oob in oob_els:
swap_type = (dom_get_attr(oob, attr) if sx_truthy(dom_get_attr(oob, attr)) else 'outerHTML')
target_id = dom_id(oob)
dom_remove_attr(oob, attr)
if sx_truthy(target_id):
results.append({'element': oob, 'swap-type': swap_type, 'target-id': target_id})
return results
# morph-node
def morph_node(old_node, new_node):
if sx_truthy((dom_has_attr_p(old_node, 'sx-preserve') if sx_truthy(dom_has_attr_p(old_node, 'sx-preserve')) else dom_has_attr_p(old_node, 'sx-ignore'))):
return NIL
elif sx_truthy((dom_has_attr_p(old_node, 'data-sx-island') if not sx_truthy(dom_has_attr_p(old_node, 'data-sx-island')) else (is_processed_p(old_node, 'island-hydrated') if not sx_truthy(is_processed_p(old_node, 'island-hydrated')) else (dom_has_attr_p(new_node, 'data-sx-island') if not sx_truthy(dom_has_attr_p(new_node, 'data-sx-island')) else (dom_get_attr(old_node, 'data-sx-island') == dom_get_attr(new_node, 'data-sx-island')))))):
return morph_island_children(old_node, new_node)
elif sx_truthy(((not sx_truthy((dom_node_type(old_node) == dom_node_type(new_node)))) if sx_truthy((not sx_truthy((dom_node_type(old_node) == dom_node_type(new_node))))) else (not sx_truthy((dom_node_name(old_node) == dom_node_name(new_node)))))):
return dom_replace_child(dom_parent(old_node), dom_clone(new_node), old_node)
elif sx_truthy(((dom_node_type(old_node) == 3) if sx_truthy((dom_node_type(old_node) == 3)) else (dom_node_type(old_node) == 8))):
if sx_truthy((not sx_truthy((dom_text_content(old_node) == dom_text_content(new_node))))):
return dom_set_text_content(old_node, dom_text_content(new_node))
return NIL
elif sx_truthy((dom_node_type(old_node) == 1)):
sync_attrs(old_node, new_node)
if sx_truthy((not sx_truthy((dom_is_active_element_p(old_node) if not sx_truthy(dom_is_active_element_p(old_node)) else dom_is_input_element_p(old_node))))):
return morph_children(old_node, new_node)
return NIL
return NIL
# sync-attrs
def sync_attrs(old_el, new_el):
ra_str = (dom_get_attr(old_el, 'data-sx-reactive-attrs') if sx_truthy(dom_get_attr(old_el, 'data-sx-reactive-attrs')) else '')
reactive_attrs = ([] if sx_truthy(empty_p(ra_str)) else split(ra_str, ','))
for attr in dom_attr_list(new_el):
name = first(attr)
val = nth(attr, 1)
if sx_truthy(((not sx_truthy((dom_get_attr(old_el, name) == val))) if not sx_truthy((not sx_truthy((dom_get_attr(old_el, name) == val)))) else (not sx_truthy(contains_p(reactive_attrs, name))))):
dom_set_attr(old_el, name, val)
return for_each(lambda attr: (lambda aname: (dom_remove_attr(old_el, aname) if sx_truthy(((not sx_truthy(dom_has_attr_p(new_el, aname))) if not sx_truthy((not sx_truthy(dom_has_attr_p(new_el, aname)))) else ((not sx_truthy(contains_p(reactive_attrs, aname))) if not sx_truthy((not sx_truthy(contains_p(reactive_attrs, aname)))) else (not sx_truthy((aname == 'data-sx-reactive-attrs')))))) else NIL))(first(attr)), dom_attr_list(old_el))
# morph-children
def morph_children(old_parent, new_parent):
# build-routing-analysis
def build_routing_analysis(pages_raw):
_cells = {}
old_kids = dom_child_list(old_parent)
new_kids = dom_child_list(new_parent)
old_by_id = reduce(lambda acc, kid: (lambda id_: (_sx_begin(_sx_dict_set(acc, id_, kid), acc) if sx_truthy(id_) else acc))(dom_id(kid)), {}, old_kids)
_cells['oi'] = 0
for new_child in new_kids:
match_id = dom_id(new_child)
match_by_id = (dict_get(old_by_id, match_id) if sx_truthy(match_id) else NIL)
if sx_truthy((match_by_id if not sx_truthy(match_by_id) else (not sx_truthy(is_nil(match_by_id))))):
if sx_truthy(((_cells['oi'] < len(old_kids)) if not sx_truthy((_cells['oi'] < len(old_kids))) else (not sx_truthy((match_by_id == nth(old_kids, _cells['oi'])))))):
dom_insert_before(old_parent, match_by_id, (nth(old_kids, _cells['oi']) if sx_truthy((_cells['oi'] < len(old_kids))) else NIL))
morph_node(match_by_id, new_child)
_cells['oi'] = (_cells['oi'] + 1)
elif sx_truthy((_cells['oi'] < len(old_kids))):
old_child = nth(old_kids, _cells['oi'])
if sx_truthy((dom_id(old_child) if not sx_truthy(dom_id(old_child)) else (not sx_truthy(match_id)))):
dom_insert_before(old_parent, dom_clone(new_child), old_child)
else:
morph_node(old_child, new_child)
_cells['oi'] = (_cells['oi'] + 1)
pages_data = []
_cells['client_count'] = 0
_cells['server_count'] = 0
for page in pages_raw:
has_data = get(page, 'has-data')
content_src = (get(page, 'content-src') if sx_truthy(get(page, 'content-src')) else '')
_cells['mode'] = NIL
_cells['reason'] = ''
if sx_truthy(has_data):
_cells['mode'] = 'server'
_cells['reason'] = 'Has :data expression — needs server IO'
_cells['server_count'] = (_cells['server_count'] + 1)
elif sx_truthy(empty_p(content_src)):
_cells['mode'] = 'server'
_cells['reason'] = 'No content expression'
_cells['server_count'] = (_cells['server_count'] + 1)
else:
dom_append(old_parent, dom_clone(new_child))
return for_each(lambda i: ((lambda leftover: (dom_remove_child(old_parent, leftover) if sx_truthy((dom_is_child_of_p(leftover, old_parent) if not sx_truthy(dom_is_child_of_p(leftover, old_parent)) else ((not sx_truthy(dom_has_attr_p(leftover, 'sx-preserve'))) if not sx_truthy((not sx_truthy(dom_has_attr_p(leftover, 'sx-preserve')))) else (not sx_truthy(dom_has_attr_p(leftover, 'sx-ignore')))))) else NIL))(nth(old_kids, i)) if sx_truthy((i >= _cells['oi'])) else NIL), range(_cells['oi'], len(old_kids)))
_cells['mode'] = 'client'
_cells['client_count'] = (_cells['client_count'] + 1)
pages_data.append({'name': get(page, 'name'), 'path': get(page, 'path'), 'mode': _cells['mode'], 'has-data': has_data, 'content-expr': (sx_str(slice(content_src, 0, 80), '...') if sx_truthy((len(content_src) > 80)) else content_src), 'reason': _cells['reason']})
return {'pages': pages_data, 'total-pages': (_cells['client_count'] + _cells['server_count']), 'client-count': _cells['client_count'], 'server-count': _cells['server_count']}
# morph-island-children
def morph_island_children(old_island, new_island):
old_lakes = dom_query_all(old_island, '[data-sx-lake]')
new_lakes = dom_query_all(new_island, '[data-sx-lake]')
old_marshes = dom_query_all(old_island, '[data-sx-marsh]')
new_marshes = dom_query_all(new_island, '[data-sx-marsh]')
new_lake_map = {}
new_marsh_map = {}
for lake in new_lakes:
id_ = dom_get_attr(lake, 'data-sx-lake')
if sx_truthy(id_):
new_lake_map[id_] = lake
for marsh in new_marshes:
id_ = dom_get_attr(marsh, 'data-sx-marsh')
if sx_truthy(id_):
new_marsh_map[id_] = marsh
for old_lake in old_lakes:
id_ = dom_get_attr(old_lake, 'data-sx-lake')
new_lake = dict_get(new_lake_map, id_)
if sx_truthy(new_lake):
sync_attrs(old_lake, new_lake)
morph_children(old_lake, new_lake)
for old_marsh in old_marshes:
id_ = dom_get_attr(old_marsh, 'data-sx-marsh')
new_marsh = dict_get(new_marsh_map, id_)
if sx_truthy(new_marsh):
morph_marsh(old_marsh, new_marsh, old_island)
return process_signal_updates(new_island)
# morph-marsh
def morph_marsh(old_marsh, new_marsh, island_el):
transform = dom_get_data(old_marsh, 'sx-marsh-transform')
env = dom_get_data(old_marsh, 'sx-marsh-env')
new_html = dom_inner_html(new_marsh)
if sx_truthy((env if not sx_truthy(env) else (new_html if not sx_truthy(new_html) else (not sx_truthy(empty_p(new_html)))))):
parsed = parse(new_html)
sx_content = (invoke(transform, parsed) if sx_truthy(transform) else parsed)
dispose_marsh_scope(old_marsh)
return with_marsh_scope(old_marsh, lambda : (lambda new_dom: _sx_begin(dom_remove_children_after(old_marsh, NIL), dom_append(old_marsh, new_dom)))(render_to_dom(sx_content, env, NIL)))
else:
sync_attrs(old_marsh, new_marsh)
return morph_children(old_marsh, new_marsh)
# process-signal-updates
def process_signal_updates(root):
signal_els = dom_query_all(root, '[data-sx-signal]')
return for_each(lambda el: (lambda spec: ((lambda colon_idx: ((lambda store_name: (lambda raw_value: _sx_begin((lambda parsed: reset_b(use_store(store_name), parsed))(json_parse(raw_value)), dom_remove_attr(el, 'data-sx-signal')))(slice(spec, (colon_idx + 1))))(slice(spec, 0, colon_idx)) if sx_truthy((colon_idx > 0)) else NIL))(index_of(spec, ':')) if sx_truthy(spec) else NIL))(dom_get_attr(el, 'data-sx-signal')), signal_els)
# swap-dom-nodes
def swap_dom_nodes(target, new_nodes, strategy):
_match = strategy
if _match == 'innerHTML':
if sx_truthy(dom_is_fragment_p(new_nodes)):
return morph_children(target, new_nodes)
else:
wrapper = dom_create_element('div', NIL)
dom_append(wrapper, new_nodes)
return morph_children(target, wrapper)
elif _match == 'outerHTML':
parent = dom_parent(target)
((lambda fc: (_sx_begin(morph_node(target, fc), (lambda sib: insert_remaining_siblings(parent, target, sib))(dom_next_sibling(fc))) if sx_truthy(fc) else dom_remove_child(parent, target)))(dom_first_child(new_nodes)) if sx_truthy(dom_is_fragment_p(new_nodes)) else morph_node(target, new_nodes))
return parent
elif _match == 'afterend':
return dom_insert_after(target, new_nodes)
elif _match == 'beforeend':
return dom_append(target, new_nodes)
elif _match == 'afterbegin':
return dom_prepend(target, new_nodes)
elif _match == 'beforebegin':
return dom_insert_before(dom_parent(target), new_nodes, target)
elif _match == 'delete':
return dom_remove_child(dom_parent(target), target)
elif _match == 'none':
return NIL
else:
if sx_truthy(dom_is_fragment_p(new_nodes)):
return morph_children(target, new_nodes)
else:
wrapper = dom_create_element('div', NIL)
dom_append(wrapper, new_nodes)
return morph_children(target, wrapper)
# insert-remaining-siblings
def insert_remaining_siblings(parent, ref_node, sib):
if sx_truthy(sib):
next = dom_next_sibling(sib)
dom_insert_after(ref_node, sib)
return insert_remaining_siblings(parent, sib, next)
return NIL
# swap-html-string
def swap_html_string(target, html, strategy):
_match = strategy
if _match == 'innerHTML':
return dom_set_inner_html(target, html)
elif _match == 'outerHTML':
parent = dom_parent(target)
dom_insert_adjacent_html(target, 'afterend', html)
dom_remove_child(parent, target)
return parent
elif _match == 'afterend':
return dom_insert_adjacent_html(target, 'afterend', html)
elif _match == 'beforeend':
return dom_insert_adjacent_html(target, 'beforeend', html)
elif _match == 'afterbegin':
return dom_insert_adjacent_html(target, 'afterbegin', html)
elif _match == 'beforebegin':
return dom_insert_adjacent_html(target, 'beforebegin', html)
elif _match == 'delete':
return dom_remove_child(dom_parent(target), target)
elif _match == 'none':
return NIL
else:
return dom_set_inner_html(target, html)
# handle-history
def handle_history(el, url, resp_headers):
push_url = dom_get_attr(el, 'sx-push-url')
replace_url = dom_get_attr(el, 'sx-replace-url')
hdr_replace = get(resp_headers, 'replace-url')
if sx_truthy(hdr_replace):
return browser_replace_state(hdr_replace)
elif sx_truthy((push_url if not sx_truthy(push_url) else (not sx_truthy((push_url == 'false'))))):
return browser_push_state((url if sx_truthy((push_url == 'true')) else push_url))
elif sx_truthy((replace_url if not sx_truthy(replace_url) else (not sx_truthy((replace_url == 'false'))))):
return browser_replace_state((url if sx_truthy((replace_url == 'true')) else replace_url))
return NIL
# PRELOAD_TTL
PRELOAD_TTL = 30000
# preload-cache-get
def preload_cache_get(cache, url):
entry = dict_get(cache, url)
if sx_truthy(is_nil(entry)):
return NIL
else:
if sx_truthy(((now_ms() - get(entry, 'timestamp')) > PRELOAD_TTL)):
dict_delete(cache, url)
return NIL
else:
dict_delete(cache, url)
return entry
# preload-cache-set
def preload_cache_set(cache, url, text, content_type):
return _sx_dict_set(cache, url, {'text': text, 'content-type': content_type, 'timestamp': now_ms()})
# classify-trigger
def classify_trigger(trigger):
event = get(trigger, 'event')
if sx_truthy((event == 'every')):
return 'poll'
elif sx_truthy((event == 'intersect')):
return 'intersect'
elif sx_truthy((event == 'load')):
return 'load'
elif sx_truthy((event == 'revealed')):
return 'revealed'
else:
return 'event'
# should-boost-link?
def should_boost_link_p(link):
href = dom_get_attr(link, 'href')
return (href if not sx_truthy(href) else ((not sx_truthy(starts_with_p(href, '#'))) if not sx_truthy((not sx_truthy(starts_with_p(href, '#')))) else ((not sx_truthy(starts_with_p(href, 'javascript:'))) if not sx_truthy((not sx_truthy(starts_with_p(href, 'javascript:')))) else ((not sx_truthy(starts_with_p(href, 'mailto:'))) if not sx_truthy((not sx_truthy(starts_with_p(href, 'mailto:')))) else (browser_same_origin_p(href) if not sx_truthy(browser_same_origin_p(href)) else ((not sx_truthy(dom_has_attr_p(link, 'sx-get'))) if not sx_truthy((not sx_truthy(dom_has_attr_p(link, 'sx-get')))) else ((not sx_truthy(dom_has_attr_p(link, 'sx-post'))) if not sx_truthy((not sx_truthy(dom_has_attr_p(link, 'sx-post')))) else (not sx_truthy(dom_has_attr_p(link, 'sx-disable'))))))))))
# should-boost-form?
def should_boost_form_p(form):
return ((not sx_truthy(dom_has_attr_p(form, 'sx-get'))) if not sx_truthy((not sx_truthy(dom_has_attr_p(form, 'sx-get')))) else ((not sx_truthy(dom_has_attr_p(form, 'sx-post'))) if not sx_truthy((not sx_truthy(dom_has_attr_p(form, 'sx-post')))) else (not sx_truthy(dom_has_attr_p(form, 'sx-disable')))))
# parse-sse-swap
def parse_sse_swap(el):
return (dom_get_attr(el, 'sx-sse-swap') if sx_truthy(dom_get_attr(el, 'sx-sse-swap')) else 'message')
# === Transpiled from router (client-side route matching) ===
# split-path-segments
def split_path_segments(path):
trimmed = (slice(path, 1) if sx_truthy(starts_with_p(path, '/')) else path)
trimmed2 = (slice(trimmed, 0, (len(trimmed) - 1)) if sx_truthy(((not sx_truthy(empty_p(trimmed))) if not sx_truthy((not sx_truthy(empty_p(trimmed)))) else ends_with_p(trimmed, '/'))) else trimmed)
if sx_truthy(empty_p(trimmed2)):
return []
else:
return split(trimmed2, '/')
# make-route-segment
def make_route_segment(seg):
if sx_truthy((starts_with_p(seg, '<') if not sx_truthy(starts_with_p(seg, '<')) else ends_with_p(seg, '>'))):
param_name = slice(seg, 1, (len(seg) - 1))
d = {}
d['type'] = 'param'
d['value'] = param_name
return d
else:
d = {}
d['type'] = 'literal'
d['value'] = seg
return d
# parse-route-pattern
def parse_route_pattern(pattern):
segments = split_path_segments(pattern)
return map(make_route_segment, segments)
# match-route-segments
def match_route_segments(path_segs, parsed_segs):
_cells = {}
if sx_truthy((not sx_truthy((len(path_segs) == len(parsed_segs))))):
return NIL
else:
params = {}
_cells['matched'] = True
for_each_indexed(lambda i, parsed_seg: ((lambda path_seg: (lambda seg_type: ((_sx_cell_set(_cells, 'matched', False) if sx_truthy((not sx_truthy((path_seg == get(parsed_seg, 'value'))))) else NIL) if sx_truthy((seg_type == 'literal')) else (_sx_dict_set(params, get(parsed_seg, 'value'), path_seg) if sx_truthy((seg_type == 'param')) else _sx_cell_set(_cells, 'matched', False))))(get(parsed_seg, 'type')))(nth(path_segs, i)) if sx_truthy(_cells['matched']) else NIL), parsed_segs)
if sx_truthy(_cells['matched']):
return params
else:
return NIL
# match-route
def match_route(path, pattern):
path_segs = split_path_segments(path)
parsed_segs = parse_route_pattern(pattern)
return match_route_segments(path_segs, parsed_segs)
# find-matching-route
def find_matching_route(path, routes):
_cells = {}
path_segs = split_path_segments(path)
_cells['result'] = NIL
for route in routes:
if sx_truthy(is_nil(_cells['result'])):
params = match_route_segments(path_segs, get(route, 'parsed'))
if sx_truthy((not sx_truthy(is_nil(params)))):
matched = merge(route, {})
matched['params'] = params
_cells['result'] = matched
return _cells['result']
# build-affinity-analysis
def build_affinity_analysis(demo_components, page_plans):
return {'components': demo_components, 'page-plans': page_plans}
# === Transpiled from signals (reactive signal runtime) ===
@@ -3058,4 +2753,4 @@ def render(expr, env=None):
def make_env(**kwargs):
"""Create an environment with initial bindings."""
return _Env(dict(kwargs))
return _Env(dict(kwargs))