6 Commits

Author SHA1 Message Date
e2940e1c5f Add Content Addressing page under CEK Machine
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 15m20s
Dedicated page documenting and demonstrating content-addressed
computation. How it works, why it matters, the path to IPFS.

Live demo: counter + name widget with CID generation, history,
and restore-from-CID input.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 00:27:14 +00:00
f7debec7c6 Content-addressed computation: freeze → hash → CID → thaw
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
Hash frozen SX to a content identifier (djb2 → hex). Same state
always produces the same CID. Store by CID, retrieve by CID.

- content-hash: djb2 hash of SX text → hex string
- content-put/get: in-memory content store
- freeze-to-cid: freeze scope → store → return CID
- thaw-from-cid: look up CID → thaw signals
- char-code-at / to-hex primitives for both platforms
- Live demo: counter + name widget, content-address button,
  CID display, restore from CID input, CID history

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 00:17:29 +00:00
488fc53fda Persist home stepper state to localStorage via freeze scope
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
- freeze-scope "home-stepper" captures step-idx signal
- Each step/back saves to localStorage via freeze-to-sx
- On mount, restores from localStorage via thaw-from-sx
- Invalid state resets to default (step 9)
- Clear preview lake before replay to prevent duplicates
- Register local-storage-get/set/remove as primitives
- Arrows 3x bigger

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 00:04:32 +00:00
cb4f4b85e5 Named freeze scopes for serializable reactive state
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m20s
Replace raw CEK state serialization with named freeze scopes.
A freeze scope collects signals registered within it. On freeze,
signal values are serialized to SX. On thaw, values are restored.

- freeze-scope: scoped effect delimiter for signal collection
- freeze-signal: register a signal with a name in the current scope
- cek-freeze-scope / cek-thaw-scope: freeze/thaw by scope name
- freeze-to-sx / thaw-from-sx: full SX text round-trip
- cek-freeze-all / cek-thaw-all: batch operations

Also: register boolean?, symbol?, keyword? predicates in both
Python and JS platforms with proper var aliases.

Demo: counter + name input with Freeze/Thaw buttons.
Frozen SX: {:name "demo" :signals {:count 5 :name "world"}}

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 23:21:38 +00:00
a759f4da3b Add Freeze/Thaw page under CEK Machine with live demo
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
Documents and demonstrates serializable CEK state. Type an expression,
step to any point, click Freeze to see the frozen SX. Click Thaw to
resume from the frozen state and get the result.

- New page at /sx/(geography.(cek.freeze))
- Nav entry under CEK Machine
- Interactive island demo with step/run/freeze/thaw buttons
- Documentation: the idea, freeze format, thaw/resume, what it enables

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 22:31:34 +00:00
b03c84b962 Serializable CEK state: cek-freeze and cek-thaw
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
Freeze a CEK state to pure s-expressions. Thaw it back to a live
state and resume with cek-run. Full round-trip through SX text works:
freeze → sx-serialize → sx-parse → thaw → resume → same result.

- cek-freeze: serialize control/env/kont/value to SX dicts
- cek-thaw: reconstruct live state from frozen SX
- Native functions serialize as (primitive "name"), looked up on resume
- Lambdas serialize as (lambda (params) body)
- Environments serialize as flat dicts of visible bindings
- Continuation frames serialize as typed dicts

Enables: localStorage persistence, content-addressed computation,
cross-machine migration, time-travel debugging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 22:11:05 +00:00
9 changed files with 755 additions and 7 deletions

View File

@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-14T20:39:57Z";
var SX_VERSION = "2026-03-15T00:27:14Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -352,6 +352,8 @@
PRIMITIVES["even?"] = function(n) { return n % 2 === 0; };
PRIMITIVES["zero?"] = function(n) { return n === 0; };
PRIMITIVES["boolean?"] = function(x) { return x === true || x === false; };
PRIMITIVES["symbol?"] = function(x) { return x != null && x._sym === true; };
PRIMITIVES["keyword?"] = function(x) { return x != null && x._kw === true; };
PRIMITIVES["component-affinity"] = componentAffinity;
@@ -525,7 +527,14 @@
function mapDict(fn, d) { var r = {}; for (var k in d) r[k] = fn(k, d[k]); return r; }
// Predicate aliases used by transpiled code
// Both naming conventions: isX (from js-renames) and x_p (from js-mangle of x?)
var isNumber = PRIMITIVES["number?"]; var number_p = isNumber;
var isString = PRIMITIVES["string?"]; var string_p = isString;
var isBoolean = PRIMITIVES["boolean?"]; var boolean_p = isBoolean;
var isDict = PRIMITIVES["dict?"];
var isList = PRIMITIVES["list?"]; var list_p = isList;
var isKeyword = PRIMITIVES["keyword?"]; var keyword_p = isKeyword;
var isSymbol = PRIMITIVES["symbol?"]; var symbol_p = isSymbol;
// List primitives used directly by transpiled code
var len = PRIMITIVES["len"];
@@ -748,6 +757,12 @@
var charFromCode = PRIMITIVES["char-from-code"];
// String/number utilities needed by transpiled spec code (content-hash etc)
PRIMITIVES["char-code-at"] = function(s, i) { return s.charCodeAt(i); };
var charCodeAt = PRIMITIVES["char-code-at"];
PRIMITIVES["to-hex"] = function(n) { return (n >>> 0).toString(16); };
var toHex = PRIMITIVES["to-hex"];
// =========================================================================
// Platform: CEK module — explicit CEK machine
// =========================================================================
@@ -5283,6 +5298,111 @@ PRIMITIVES["eval-expr-cek"] = evalExprCek;
var trampolineCek = function(val) { return (isSxTruthy(isThunk(val)) ? evalExprCek(thunkExpr(val), thunkEnv(val)) : val); };
PRIMITIVES["trampoline-cek"] = trampolineCek;
// freeze-registry
var freezeRegistry = {};
PRIMITIVES["freeze-registry"] = freezeRegistry;
// freeze-signal
var freezeSignal = function(name, sig) { return (function() {
var scopeName = sxContext("sx-freeze-scope", NIL);
return (isSxTruthy(scopeName) ? (function() {
var entries = sxOr(get(freezeRegistry, scopeName), []);
entries.push({["name"]: name, ["signal"]: sig});
return dictSet(freezeRegistry, scopeName, entries);
})() : NIL);
})(); };
PRIMITIVES["freeze-signal"] = freezeSignal;
// freeze-scope
var freezeScope = function(name, bodyFn) { scopePush("sx-freeze-scope", name);
freezeRegistry[name] = [];
cekCall(bodyFn, NIL);
scopePop("sx-freeze-scope");
return NIL; };
PRIMITIVES["freeze-scope"] = freezeScope;
// cek-freeze-scope
var cekFreezeScope = function(name) { return (function() {
var entries = sxOr(get(freezeRegistry, name), []);
var signalsDict = {};
{ var _c = entries; for (var _i = 0; _i < _c.length; _i++) { var entry = _c[_i]; signalsDict[get(entry, "name")] = signalValue(get(entry, "signal")); } }
return {["name"]: name, ["signals"]: signalsDict};
})(); };
PRIMITIVES["cek-freeze-scope"] = cekFreezeScope;
// cek-freeze-all
var cekFreezeAll = function() { return map(function(name) { return cekFreezeScope(name); }, keys(freezeRegistry)); };
PRIMITIVES["cek-freeze-all"] = cekFreezeAll;
// cek-thaw-scope
var cekThawScope = function(name, frozen) { return (function() {
var entries = sxOr(get(freezeRegistry, name), []);
var values = get(frozen, "signals");
return (isSxTruthy(values) ? forEach(function(entry) { return (function() {
var sigName = get(entry, "name");
var sig = get(entry, "signal");
var val = get(values, sigName);
return (isSxTruthy(!isSxTruthy(isNil(val))) ? reset_b(sig, val) : NIL);
})(); }, entries) : NIL);
})(); };
PRIMITIVES["cek-thaw-scope"] = cekThawScope;
// cek-thaw-all
var cekThawAll = function(frozenList) { return forEach(function(frozen) { return cekThawScope(get(frozen, "name"), frozen); }, frozenList); };
PRIMITIVES["cek-thaw-all"] = cekThawAll;
// freeze-to-sx
var freezeToSx = function(name) { return sxSerialize(cekFreezeScope(name)); };
PRIMITIVES["freeze-to-sx"] = freezeToSx;
// thaw-from-sx
var thawFromSx = function(sxText) { return (function() {
var parsed = sxParse(sxText);
return (isSxTruthy(!isSxTruthy(isEmpty(parsed))) ? (function() {
var frozen = first(parsed);
return cekThawScope(get(frozen, "name"), frozen);
})() : NIL);
})(); };
PRIMITIVES["thaw-from-sx"] = thawFromSx;
// content-store
var contentStore = {};
PRIMITIVES["content-store"] = contentStore;
// content-hash
var contentHash = function(sxText) { return (function() {
var hash = 5381;
{ var _c = range(0, len(sxText)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; hash = (((hash * 33) + charCodeAt(sxText, i)) % 4294967296); } }
return toHex(hash);
})(); };
PRIMITIVES["content-hash"] = contentHash;
// content-put
var contentPut = function(sxText) { return (function() {
var cid = contentHash(sxText);
contentStore[cid] = sxText;
return cid;
})(); };
PRIMITIVES["content-put"] = contentPut;
// content-get
var contentGet = function(cid) { return get(contentStore, cid); };
PRIMITIVES["content-get"] = contentGet;
// freeze-to-cid
var freezeToCid = function(scopeName) { return (function() {
var sxText = freezeToSx(scopeName);
return contentPut(sxText);
})(); };
PRIMITIVES["freeze-to-cid"] = freezeToCid;
// thaw-from-cid
var thawFromCid = function(cid) { return (function() {
var sxText = contentGet(cid);
return (isSxTruthy(sxText) ? (thawFromSx(sxText), true) : NIL);
})(); };
PRIMITIVES["thaw-from-cid"] = thawFromCid;
// === Transpiled from signals (reactive signal runtime) ===
@@ -5681,6 +5801,20 @@ PRIMITIVES["resource"] = resource;
PRIMITIVES["is-html-tag?"] = function(n) { return HTML_TAGS.indexOf(n) >= 0; };
PRIMITIVES["make-env"] = function() { return merge(componentEnv, PRIMITIVES); };
// localStorage — defined here (before boot) so islands can use at hydration
PRIMITIVES["local-storage-get"] = function(key) {
try { var v = localStorage.getItem(key); return v === null ? NIL : v; }
catch (e) { return NIL; }
};
PRIMITIVES["local-storage-set"] = function(key, val) {
try { localStorage.setItem(key, val); } catch (e) {}
return NIL;
};
PRIMITIVES["local-storage-remove"] = function(key) {
try { localStorage.removeItem(key); } catch (e) {}
return NIL;
};
// =========================================================================
// Platform interface — DOM adapter (browser-only)
@@ -6943,6 +7077,7 @@ PRIMITIVES["resource"] = resource;
function localStorageRemove(key) {
try { localStorage.removeItem(key); } catch (e) {}
}
// localStorage primitives registered in CEK_FIXUPS_JS for ordering
// --- Cookies ---

View File

@@ -1032,3 +1032,147 @@
(if (thunk? val)
(eval-expr-cek (thunk-expr val) (thunk-env val))
val)))
;; --------------------------------------------------------------------------
;; 13. Freeze scopes — named serializable state boundaries
;; --------------------------------------------------------------------------
;;
;; A freeze scope collects signals registered within it. On freeze,
;; their current values are serialized to SX. On thaw, values are
;; restored. Multiple named scopes can coexist independently.
;;
;; Uses the scoped effects system: scope-push!/scope-pop!/context.
;;
;; Usage:
;; (freeze-scope "editor"
;; (let ((doc (signal "hello")))
;; (freeze-signal "doc" doc)
;; ...))
;;
;; (cek-freeze-scope "editor") → {:name "editor" :signals {:doc "hello"}}
;; (cek-thaw-scope "editor" frozen-data) → restores signal values
;; Registry of freeze scopes: name → list of {name signal} entries
(define freeze-registry (dict))
;; Register a signal in the current freeze scope
(define freeze-signal :effects [mutation]
(fn (name sig)
(let ((scope-name (context "sx-freeze-scope" nil)))
(when scope-name
(let ((entries (or (get freeze-registry scope-name) (list))))
(append! entries (dict "name" name "signal" sig))
(dict-set! freeze-registry scope-name entries))))))
;; Freeze scope delimiter — collects signals registered within body
(define freeze-scope :effects [mutation]
(fn (name body-fn)
(scope-push! "sx-freeze-scope" name)
;; Initialize empty entry list for this scope
(dict-set! freeze-registry name (list))
(cek-call body-fn nil)
(scope-pop! "sx-freeze-scope")
nil))
;; Freeze a named scope → SX dict of signal values
(define cek-freeze-scope :effects []
(fn (name)
(let ((entries (or (get freeze-registry name) (list)))
(signals-dict (dict)))
(for-each (fn (entry)
(dict-set! signals-dict
(get entry "name")
(signal-value (get entry "signal"))))
entries)
(dict "name" name "signals" signals-dict))))
;; Freeze all scopes
(define cek-freeze-all :effects []
(fn ()
(map (fn (name) (cek-freeze-scope name))
(keys freeze-registry))))
;; Thaw a named scope — restore signal values from frozen data
(define cek-thaw-scope :effects [mutation]
(fn (name frozen)
(let ((entries (or (get freeze-registry name) (list)))
(values (get frozen "signals")))
(when values
(for-each (fn (entry)
(let ((sig-name (get entry "name"))
(sig (get entry "signal"))
(val (get values sig-name)))
(when (not (nil? val))
(reset! sig val))))
entries)))))
;; Thaw all scopes from a list of frozen scope dicts
(define cek-thaw-all :effects [mutation]
(fn (frozen-list)
(for-each (fn (frozen)
(cek-thaw-scope (get frozen "name") frozen))
frozen-list)))
;; Serialize a frozen scope to SX text
(define freeze-to-sx :effects []
(fn (name)
(sx-serialize (cek-freeze-scope name))))
;; Restore from SX text
(define thaw-from-sx :effects [mutation]
(fn (sx-text)
(let ((parsed (sx-parse sx-text)))
(when (not (empty? parsed))
(let ((frozen (first parsed)))
(cek-thaw-scope (get frozen "name") frozen))))))
;; --------------------------------------------------------------------------
;; 14. Content-addressed computation
;; --------------------------------------------------------------------------
;;
;; Hash frozen SX to a content identifier. Store and retrieve by CID.
;; The content IS the address — same SX always produces the same CID.
;;
;; Uses an in-memory content store. Applications can persist to
;; localStorage or IPFS by providing their own store backend.
(define content-store (dict))
(define content-hash :effects []
(fn (sx-text)
;; djb2 hash → hex string. Simple, deterministic, fast.
;; Real deployment would use SHA-256 / multihash.
(let ((hash 5381))
(for-each (fn (i)
(set! hash (mod (+ (* hash 33) (char-code-at sx-text i)) 4294967296)))
(range 0 (len sx-text)))
(to-hex hash))))
(define content-put :effects [mutation]
(fn (sx-text)
(let ((cid (content-hash sx-text)))
(dict-set! content-store cid sx-text)
cid)))
(define content-get :effects []
(fn (cid)
(get content-store cid)))
;; Freeze a scope → store → return CID
(define freeze-to-cid :effects [mutation]
(fn (scope-name)
(let ((sx-text (freeze-to-sx scope-name)))
(content-put sx-text))))
;; Thaw from CID → look up → restore
(define thaw-from-cid :effects [mutation]
(fn (cid)
(let ((sx-text (content-get cid)))
(when sx-text
(thaw-from-sx sx-text)
true))))

View File

@@ -951,6 +951,8 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
PRIMITIVES["even?"] = function(n) { return n % 2 === 0; };
PRIMITIVES["zero?"] = function(n) { return n === 0; };
PRIMITIVES["boolean?"] = function(x) { return x === true || x === false; };
PRIMITIVES["symbol?"] = function(x) { return x != null && x._sym === true; };
PRIMITIVES["keyword?"] = function(x) { return x != null && x._kw === true; };
PRIMITIVES["component-affinity"] = componentAffinity;
''',
@@ -1353,7 +1355,14 @@ PLATFORM_JS_POST = '''
function mapDict(fn, d) { var r = {}; for (var k in d) r[k] = fn(k, d[k]); return r; }
// Predicate aliases used by transpiled code
// Both naming conventions: isX (from js-renames) and x_p (from js-mangle of x?)
var isNumber = PRIMITIVES["number?"]; var number_p = isNumber;
var isString = PRIMITIVES["string?"]; var string_p = isString;
var isBoolean = PRIMITIVES["boolean?"]; var boolean_p = isBoolean;
var isDict = PRIMITIVES["dict?"];
var isList = PRIMITIVES["list?"]; var list_p = isList;
var isKeyword = PRIMITIVES["keyword?"]; var keyword_p = isKeyword;
var isSymbol = PRIMITIVES["symbol?"]; var symbol_p = isSymbol;
// List primitives used directly by transpiled code
var len = PRIMITIVES["len"];
@@ -1472,6 +1481,12 @@ PLATFORM_JS_POST = '''
PLATFORM_CEK_JS = '''
// String/number utilities needed by transpiled spec code (content-hash etc)
PRIMITIVES["char-code-at"] = function(s, i) { return s.charCodeAt(i); };
var charCodeAt = PRIMITIVES["char-code-at"];
PRIMITIVES["to-hex"] = function(n) { return (n >>> 0).toString(16); };
var toHex = PRIMITIVES["to-hex"];
// =========================================================================
// Platform: CEK module — explicit CEK machine
// =========================================================================
@@ -1522,6 +1537,20 @@ CEK_FIXUPS_JS = '''
PRIMITIVES["make-symbol"] = function(n) { return new Symbol(n); };
PRIMITIVES["is-html-tag?"] = function(n) { return HTML_TAGS.indexOf(n) >= 0; };
PRIMITIVES["make-env"] = function() { return merge(componentEnv, PRIMITIVES); };
// localStorage — defined here (before boot) so islands can use at hydration
PRIMITIVES["local-storage-get"] = function(key) {
try { var v = localStorage.getItem(key); return v === null ? NIL : v; }
catch (e) { return NIL; }
};
PRIMITIVES["local-storage-set"] = function(key, val) {
try { localStorage.setItem(key, val); } catch (e) {}
return NIL;
};
PRIMITIVES["local-storage-remove"] = function(key) {
try { localStorage.removeItem(key); } catch (e) {}
return NIL;
};
'''
@@ -2903,6 +2932,7 @@ PLATFORM_BOOT_JS = """
function localStorageRemove(key) {
try { localStorage.removeItem(key); } catch (e) {}
}
// localStorage primitives registered in CEK_FIXUPS_JS for ordering
// --- Cookies ---

View File

@@ -721,6 +721,9 @@ PRIMITIVES["number?"] = lambda x: isinstance(x, (int, float)) and not isinstance
PRIMITIVES["string?"] = lambda x: isinstance(x, str)
PRIMITIVES["list?"] = lambda x: isinstance(x, _b_list)
PRIMITIVES["dict?"] = lambda x: isinstance(x, _b_dict)
PRIMITIVES["boolean?"] = lambda x: isinstance(x, bool)
PRIMITIVES["symbol?"] = lambda x: isinstance(x, Symbol)
PRIMITIVES["keyword?"] = lambda x: isinstance(x, Keyword)
PRIMITIVES["continuation?"] = lambda x: isinstance(x, Continuation)
PRIMITIVES["empty?"] = lambda c: (
c is None or c is NIL or
@@ -1010,7 +1013,17 @@ parse_int = PRIMITIVES["parse-int"]
upper = PRIMITIVES["upper"]
has_key_p = PRIMITIVES["has-key?"]
dict_p = PRIMITIVES["dict?"]
boolean_p = PRIMITIVES["boolean?"]
symbol_p = PRIMITIVES["symbol?"]
keyword_p = PRIMITIVES["keyword?"]
number_p = PRIMITIVES["number?"]
string_p = PRIMITIVES["string?"]
list_p = PRIMITIVES["list?"]
dissoc = PRIMITIVES["dissoc"]
PRIMITIVES["char-code-at"] = lambda s, i: ord(s[int(i)]) if 0 <= int(i) < len(s) else 0
PRIMITIVES["to-hex"] = lambda n: hex(int(n) & 0xFFFFFFFF)[2:]
char_code_at = PRIMITIVES["char-code-at"]
to_hex = PRIMITIVES["to-hex"]
index_of = PRIMITIVES["index-of"]
lower = PRIMITIVES["lower"]
char_from_code = PRIMITIVES["char-from-code"]

View File

@@ -692,6 +692,9 @@ PRIMITIVES["number?"] = lambda x: isinstance(x, (int, float)) and not isinstance
PRIMITIVES["string?"] = lambda x: isinstance(x, str)
PRIMITIVES["list?"] = lambda x: isinstance(x, _b_list)
PRIMITIVES["dict?"] = lambda x: isinstance(x, _b_dict)
PRIMITIVES["boolean?"] = lambda x: isinstance(x, bool)
PRIMITIVES["symbol?"] = lambda x: isinstance(x, Symbol)
PRIMITIVES["keyword?"] = lambda x: isinstance(x, Keyword)
PRIMITIVES["continuation?"] = lambda x: isinstance(x, Continuation)
PRIMITIVES["empty?"] = lambda c: (
c is None or c is NIL or
@@ -930,7 +933,17 @@ parse_int = PRIMITIVES["parse-int"]
upper = PRIMITIVES["upper"]
has_key_p = PRIMITIVES["has-key?"]
dict_p = PRIMITIVES["dict?"]
boolean_p = PRIMITIVES["boolean?"]
symbol_p = PRIMITIVES["symbol?"]
keyword_p = PRIMITIVES["keyword?"]
number_p = PRIMITIVES["number?"]
string_p = PRIMITIVES["string?"]
list_p = PRIMITIVES["list?"]
dissoc = PRIMITIVES["dissoc"]
PRIMITIVES["char-code-at"] = lambda s, i: ord(s[int(i)]) if 0 <= int(i) < len(s) else 0
PRIMITIVES["to-hex"] = lambda n: hex(int(n) & 0xFFFFFFFF)[2:]
char_code_at = PRIMITIVES["char-code-at"]
to_hex = PRIMITIVES["to-hex"]
index_of = PRIMITIVES["index-of"]
lower = PRIMITIVES["lower"]
char_from_code = PRIMITIVES["char-from-code"]
@@ -4586,6 +4599,104 @@ def trampoline_cek(val):
else:
return val
# freeze-registry
freeze_registry = {}
# freeze-signal
def freeze_signal(name, sig):
scope_name = sx_context('sx-freeze-scope', NIL)
if sx_truthy(scope_name):
entries = (get(freeze_registry, scope_name) if sx_truthy(get(freeze_registry, scope_name)) else [])
entries.append({'name': name, 'signal': sig})
return _sx_dict_set(freeze_registry, scope_name, entries)
return NIL
# freeze-scope
def freeze_scope(name, body_fn):
scope_push('sx-freeze-scope', name)
freeze_registry[name] = []
cek_call(body_fn, NIL)
scope_pop('sx-freeze-scope')
return NIL
# cek-freeze-scope
def cek_freeze_scope(name):
entries = (get(freeze_registry, name) if sx_truthy(get(freeze_registry, name)) else [])
signals_dict = {}
for entry in entries:
signals_dict[get(entry, 'name')] = signal_value(get(entry, 'signal'))
return {'name': name, 'signals': signals_dict}
# cek-freeze-all
def cek_freeze_all():
return map(lambda name: cek_freeze_scope(name), keys(freeze_registry))
# cek-thaw-scope
def cek_thaw_scope(name, frozen):
entries = (get(freeze_registry, name) if sx_truthy(get(freeze_registry, name)) else [])
values = get(frozen, 'signals')
if sx_truthy(values):
for entry in entries:
sig_name = get(entry, 'name')
sig = get(entry, 'signal')
val = get(values, sig_name)
if sx_truthy((not sx_truthy(is_nil(val)))):
reset_b(sig, val)
return NIL
return NIL
# cek-thaw-all
def cek_thaw_all(frozen_list):
for frozen in frozen_list:
cek_thaw_scope(get(frozen, 'name'), frozen)
return NIL
# freeze-to-sx
def freeze_to_sx(name):
return sx_serialize(cek_freeze_scope(name))
# thaw-from-sx
def thaw_from_sx(sx_text):
parsed = sx_parse(sx_text)
if sx_truthy((not sx_truthy(empty_p(parsed)))):
frozen = first(parsed)
return cek_thaw_scope(get(frozen, 'name'), frozen)
return NIL
# content-store
content_store = {}
# content-hash
def content_hash(sx_text):
_cells = {}
_cells['hash'] = 5381
for i in range(0, len(sx_text)):
_cells['hash'] = (((_cells['hash'] * 33) + char_code_at(sx_text, i)) % 4294967296)
return to_hex(_cells['hash'])
# content-put
def content_put(sx_text):
cid = content_hash(sx_text)
content_store[cid] = sx_text
return cid
# content-get
def content_get(cid):
return get(content_store, cid)
# freeze-to-cid
def freeze_to_cid(scope_name):
sx_text = freeze_to_sx(scope_name)
return content_put(sx_text)
# thaw-from-cid
def thaw_from_cid(cid):
sx_text = content_get(cid)
if sx_truthy(sx_text):
thaw_from_sx(sx_text)
return True
return NIL
# === Transpiled from signals (reactive signal runtime) ===

View File

@@ -473,6 +473,301 @@
;; ---------------------------------------------------------------------------
;; CEK Freeze / Thaw — serializable computation
;; ---------------------------------------------------------------------------
(defisland ~geography/cek/content-address-demo ()
(let ((count (signal 0))
(name (signal "world"))
(cid-display (signal ""))
(cid-input (signal ""))
(cid-history (signal (list)))
(status (signal "")))
;; Register in freeze scope
(freeze-scope "ca-demo" (fn ()
(freeze-signal "count" count)
(freeze-signal "name" name)))
(div (~cssx/tw :tokens "space-y-4")
;; Interactive widget
(div (~cssx/tw :tokens "flex gap-4 items-center")
(div (~cssx/tw :tokens "flex items-center gap-2")
(button :on-click (fn (e) (swap! count dec))
(~cssx/tw :tokens "px-2 py-1 rounded bg-stone-200 text-stone-700 hover:bg-stone-300") "-")
(span (~cssx/tw :tokens "font-mono text-lg font-bold w-8 text-center") (deref count))
(button :on-click (fn (e) (swap! count inc))
(~cssx/tw :tokens "px-2 py-1 rounded bg-stone-200 text-stone-700 hover:bg-stone-300") "+"))
(input :type "text" :bind name
(~cssx/tw :tokens "px-3 py-1 rounded border border-stone-300 font-mono text-sm")))
;; Live output
(div (~cssx/tw :tokens "rounded bg-violet-50 border border-violet-200 p-3 text-violet-800")
"Hello, " (deref name) "! Count = " (deref count))
;; Content address button
(div (~cssx/tw :tokens "flex gap-2")
(button :on-click (fn (e)
(let ((cid (freeze-to-cid "ca-demo")))
(reset! cid-display cid)
(reset! status (str "Stored as " cid))
(swap! cid-history (fn (h) (append h (list cid))))))
(~cssx/tw :tokens "px-3 py-1.5 rounded bg-stone-700 text-white text-sm hover:bg-stone-800")
"Content-address"))
;; CID display
(when (not (empty? (deref cid-display)))
(div (~cssx/tw :tokens "font-mono text-sm bg-stone-50 rounded p-2 flex items-center gap-2")
(span (~cssx/tw :tokens "text-stone-400") "CID:")
(span (~cssx/tw :tokens "text-violet-700 font-bold") (deref cid-display))))
;; Status
(when (not (empty? (deref status)))
(div (~cssx/tw :tokens "text-xs text-emerald-600") (deref status)))
;; Restore from CID
(div (~cssx/tw :tokens "flex gap-2 items-end")
(div (~cssx/tw :tokens "flex-1")
(label (~cssx/tw :tokens "text-xs text-stone-400 block mb-1") "Restore from CID")
(input :type "text" :bind cid-input
(~cssx/tw :tokens "w-full px-3 py-1 rounded border border-stone-300 font-mono text-sm")))
(button :on-click (fn (e)
(if (thaw-from-cid (deref cid-input))
(reset! status (str "Restored from " (deref cid-input)))
(reset! status (str "CID not found: " (deref cid-input)))))
(~cssx/tw :tokens "px-3 py-1.5 rounded bg-emerald-600 text-white text-sm hover:bg-emerald-700")
"Restore"))
;; CID history
(when (not (empty? (deref cid-history)))
(div (~cssx/tw :tokens "space-y-1")
(label (~cssx/tw :tokens "text-xs text-stone-400 block") "History")
(map (fn (cid)
(button :on-click (fn (e)
(if (thaw-from-cid cid)
(reset! status (str "Restored from " cid))
(reset! status (str "CID not found: " cid))))
(~cssx/tw :tokens "block w-full text-left px-2 py-1 rounded bg-stone-50 hover:bg-stone-100 font-mono text-xs text-stone-600")
cid))
(deref cid-history)))))))
(defcomp ~geography/cek/cek-freeze-content ()
(~docs/page :title "Freeze / Thaw"
(p :class "text-stone-500 text-sm italic mb-8"
"A computation is a value. Freeze it to an s-expression. "
"Store it, transmit it, content-address it. Thaw and resume anywhere.")
(~docs/section :title "The idea" :id "idea"
(p "The CEK machine makes evaluation explicit: every step is a pure function from state to state. "
"The state is a dict with four fields:")
(ul :class "list-disc pl-6 mb-4 space-y-1 text-stone-600"
(li (code "control") " \u2014 the expression being evaluated")
(li (code "env") " \u2014 the bindings in scope")
(li (code "kont") " \u2014 the continuation (what to do with the result)")
(li (code "phase") " \u2014 eval or continue"))
(p "Since the state is data, it can be serialized. "
(code "cek-freeze") " converts a live CEK state to pure s-expressions. "
(code "cek-thaw") " reconstructs a live state from frozen SX. "
(code "cek-run") " resumes from where it left off."))
(~docs/section :title "Freeze" :id "freeze"
(p "Take a computation mid-flight and freeze it:")
(~docs/code :code (highlight
"(let ((expr (sx-parse \"(+ 1 (* 2 3))\"))\n (state (make-cek-state (first expr) (make-env) (list))))\n ;; Step 4 times\n (set! state (cek-step (cek-step (cek-step (cek-step state)))))\n ;; Freeze to SX\n (cek-freeze state))"
"lisp"))
(p "The frozen state is pure SX:")
(~docs/code :code (highlight
"{:phase \"continue\"\n :control nil\n :value 1\n :env {}\n :kont ({:type \"arg\"\n :f (primitive \"+\")\n :evaled ()\n :remaining ((* 2 3))\n :env {}})}"
"lisp"))
(p "Everything is data. The continuation frame says: \u201cI was adding 1 to something, "
"and I still need to evaluate " (code "(* 2 3)") ".\u201d"))
(~docs/section :title "Thaw and resume" :id "thaw"
(p "Parse the frozen SX back. Thaw it. Resume:")
(~docs/code :code (highlight
"(let ((frozen (sx-parse frozen-text))\n (state (cek-thaw (first frozen))))\n (cek-run state))\n;; => 7"
"lisp"))
(p "Native functions like " (code "+") " serialize as " (code "(primitive \"+\")")
" and are looked up in the primitive registry on thaw. "
"Lambdas serialize as their source AST \u2014 " (code "(lambda (x) (* x 2))")
" \u2014 and reconstruct as callable functions."))
(~docs/section :title "Live demo" :id "demo"
(p "Type an expression, step to any point, freeze the state. "
"The frozen SX appears below. Click Thaw to resume from the frozen state.")
(~geography/cek/freeze-demo))
(~docs/section :title "Content addressing" :id "content-addressing"
(p "Hash the frozen SX " (code "\u2192") " content identifier. "
"Same state always produces the same CID. Store by CID, retrieve by CID, verify by CID.")
(~docs/code :code (highlight
"(freeze-to-cid \"widget\")\n;; => \"d9eea67b\"\n\n(thaw-from-cid \"d9eea67b\")\n;; Signals restored. Same CID = same state."
"lisp"))
(p "Try it: change the values, click Content-address. Copy the CID. "
"Change the values again. Paste the CID and Restore.")
(~geography/cek/content-address-demo))
(~docs/section :title "What this enables" :id "enables"
(ul :class "list-disc pl-6 mb-4 space-y-2 text-stone-600"
(li (strong "Persistence") " \u2014 save reactive island state to localStorage, "
"resume on page reload")
(li (strong "Migration") " \u2014 freeze a computation on one machine, "
"thaw on another. Same result, deterministically.")
(li (strong "Content addressing") " \u2014 hash the frozen SX \u2192 CID. "
"A pointer to a computation in progress, not just a value.")
(li (strong "Time travel") " \u2014 freeze at each step, store the history. "
"Jump to any point. Undo. Branch.")
(li (strong "Verification") " \u2014 re-run from a frozen state, "
"check the result matches. Reproducible computation."))
(p "The Platonic argument made concrete: a computation IS a value. "
"The Form persists. The instance resumes."))))
(defisland ~geography/cek/freeze-demo ()
(let ((bg (signal "violet"))
(size (signal "text-2xl"))
(weight (signal "font-bold"))
(text (signal "the joy of sx"))
(saved (signal (list))))
;; Register in freeze scope
(freeze-scope "widget" (fn ()
(freeze-signal "bg" bg)
(freeze-signal "size" size)
(freeze-signal "weight" weight)
(freeze-signal "text" text)))
;; Preload all dynamic colour variants
(span (~cssx/tw :tokens "hidden bg-violet-50 bg-rose-50 bg-emerald-50 bg-amber-50 bg-sky-50 bg-stone-50 border-violet-200 border-rose-200 border-emerald-200 border-amber-200 border-sky-200 border-stone-200 text-violet-700 text-rose-700 text-emerald-700 text-amber-700 text-sky-700 text-stone-700"))
(div (~cssx/tw :tokens "space-y-4")
;; Live preview
(div (~cssx/tw :tokens "p-6 rounded-lg text-center transition-all border")
:class (str "bg-" (deref bg) "-50 border-" (deref bg) "-200")
(span :class (str (deref size) " " (deref weight) " text-" (deref bg) "-700")
(deref text)))
;; Controls
(div (~cssx/tw :tokens "grid grid-cols-2 gap-3")
(div
(label (~cssx/tw :tokens "text-xs text-stone-400 block mb-1") "Colour")
(div (~cssx/tw :tokens "flex gap-1")
(button :on-click (fn (e) (reset! bg "violet"))
(~cssx/tw :tokens "w-8 h-8 rounded-full")
:class (str "bg-violet-400" (if (= (deref bg) "violet") " ring-2 ring-offset-1 ring-stone-400" ""))
"")
(button :on-click (fn (e) (reset! bg "rose"))
(~cssx/tw :tokens "w-8 h-8 rounded-full")
:class (str "bg-rose-400" (if (= (deref bg) "rose") " ring-2 ring-offset-1 ring-stone-400" ""))
"")
(button :on-click (fn (e) (reset! bg "emerald"))
(~cssx/tw :tokens "w-8 h-8 rounded-full")
:class (str "bg-emerald-400" (if (= (deref bg) "emerald") " ring-2 ring-offset-1 ring-stone-400" ""))
"")
(button :on-click (fn (e) (reset! bg "amber"))
(~cssx/tw :tokens "w-8 h-8 rounded-full")
:class (str "bg-amber-400" (if (= (deref bg) "amber") " ring-2 ring-offset-1 ring-stone-400" ""))
"")
(button :on-click (fn (e) (reset! bg "sky"))
(~cssx/tw :tokens "w-8 h-8 rounded-full")
:class (str "bg-sky-400" (if (= (deref bg) "sky") " ring-2 ring-offset-1 ring-stone-400" ""))
"")
(button :on-click (fn (e) (reset! bg "stone"))
(~cssx/tw :tokens "w-8 h-8 rounded-full")
:class (str "bg-stone-400" (if (= (deref bg) "stone") " ring-2 ring-offset-1 ring-stone-400" ""))
"")))
(div
(label (~cssx/tw :tokens "text-xs text-stone-400 block mb-1") "Size")
(div (~cssx/tw :tokens "flex gap-1")
(map (fn (s)
(button :on-click (fn (e) (reset! size s))
(~cssx/tw :tokens "px-2 py-1 rounded text-xs")
:class (if (= (deref size) s)
"bg-stone-700 text-white" "bg-stone-100 text-stone-600")
s))
(list "text-sm" "text-lg" "text-2xl" "text-4xl"))))
(div
(label (~cssx/tw :tokens "text-xs text-stone-400 block mb-1") "Weight")
(div (~cssx/tw :tokens "flex gap-1")
(map (fn (w)
(button :on-click (fn (e) (reset! weight w))
(~cssx/tw :tokens "px-2 py-1 rounded text-xs")
:class (if (= (deref weight) w)
"bg-stone-700 text-white" "bg-stone-100 text-stone-600")
w))
(list "font-normal" "font-bold" "font-semibold"))))
(div
(label (~cssx/tw :tokens "text-xs text-stone-400 block mb-1") "Text")
(input :type "text" :bind text
(~cssx/tw :tokens "w-full px-2 py-1 rounded border border-stone-300 text-sm"))))
;; Freeze / Thaw
(div (~cssx/tw :tokens "flex gap-2 items-center")
(button :on-click (fn (e)
(let ((sx (freeze-to-sx "widget")))
(swap! saved (fn (l) (append l (list sx))))))
(~cssx/tw :tokens "px-3 py-1.5 rounded bg-amber-500 text-white text-sm hover:bg-amber-600")
"Save config")
(span (~cssx/tw :tokens "text-xs text-stone-400")
(str (len (deref saved)) " saved")))
;; Saved configs
(when (not (empty? (deref saved)))
(div (~cssx/tw :tokens "space-y-1")
(label (~cssx/tw :tokens "text-xs text-stone-400 block") "Saved configs")
(map-indexed (fn (i sx)
(button :on-click (fn (e) (thaw-from-sx sx))
(~cssx/tw :tokens "block w-full text-left px-2 py-1 rounded bg-stone-50 hover:bg-stone-100 font-mono text-xs text-stone-600 truncate")
(str "Config " (+ i 1))))
(deref saved)))))))
(defcomp ~geography/cek/cek-content-address-content ()
(~docs/page :title "Content-Addressed Computation"
(p :class "text-stone-500 text-sm italic mb-8"
"A computation is a value. A value has a hash. The hash is the address. "
"Same state, same address, forever.")
(~docs/section :title "The idea" :id "idea"
(p "Freeze a scope to SX. Hash the SX text. The hash is a content identifier (CID). "
"Store the frozen SX keyed by CID. Later, look up the CID, thaw, resume.")
(p "The critical property: "
(strong "same state always produces the same CID") ". "
"Two machines freezing identical signal values get identical CIDs. "
"The address IS the content."))
(~docs/section :title "How it works" :id "how"
(~docs/code :code (highlight
";; Freeze a scope \u2192 hash \u2192 CID\n(freeze-to-cid \"widget\")\n;; => \"d9eea67b\"\n\n;; The frozen SX is stored by CID\n(content-get \"d9eea67b\")\n;; => {:name \"widget\" :signals {:count 42 :name \"hello\"}}\n\n;; Thaw from CID \u2192 signals restored\n(thaw-from-cid \"d9eea67b\")\n;; Signals reset to frozen values"
"lisp"))
(p "The hash is djb2 for now \u2014 deterministic and fast. "
"Real deployment uses SHA-256 / multihash for IPFS compatibility."))
(~docs/section :title "Why it matters" :id "why"
(ul :class "list-disc pl-6 mb-4 space-y-2 text-stone-600"
(li (strong "Sharing") " \u2014 send a CID, not a blob. "
"The receiver looks up the CID and gets the exact state.")
(li (strong "Deduplication") " \u2014 same state = same CID. "
"Store once, reference many times.")
(li (strong "Verification") " \u2014 re-freeze, compare CIDs. "
"If they match, the state is identical. No diffing needed.")
(li (strong "History") " \u2014 each CID is a snapshot. "
"A sequence of CIDs is a complete undo history.")
(li (strong "Distribution") " \u2014 CIDs on IPFS are global. "
"Pin a widget state, anyone can thaw it. "
"No server, no API, no account.")))
(~docs/section :title "Live demo" :id "demo"
(p "Change the counter and name. Click " (strong "Content-address") " to freeze and hash. "
"The CID appears below. Change the values, then click any CID in the history "
"or paste one into the input to restore.")
(~geography/cek/content-address-demo))
(~docs/section :title "The path to IPFS" :id "ipfs"
(p "The content store is currently in-memory. The next steps:")
(ul :class "list-disc pl-6 mb-4 space-y-1 text-stone-600"
(li "Replace djb2 with SHA-256 (browser SubtleCrypto)")
(li "Wrap in multihash + CIDv1 format")
(li "Store to IPFS via the Art DAG L2 registry")
(li "Pin CIDs attributed to " (code "sx-web.org"))
(li "Anyone can pin, fork, extend"))
(p "The frozen SX is the content. The CID is the address. "
"IPFS is the network. The widget state becomes a permanent, "
"verifiable, shareable artifact \u2014 not trapped in a database, "
"not behind an API, not owned by anyone."))))
;; ---------------------------------------------------------------------------
;; Demo page content
;; ---------------------------------------------------------------------------

View File

@@ -174,7 +174,8 @@
(when (and parent rendered)
(dom-append parent rendered)))))
(swap! step-idx inc)
(update-code-highlight))))
(update-code-highlight)
(local-storage-set "sx-home-stepper" (freeze-to-sx "home-stepper")))))
(do-back (fn ()
(when (> (deref step-idx) 0)
(let ((target (- (deref step-idx) 1))
@@ -182,7 +183,18 @@
(when container (dom-set-prop container "innerHTML" ""))
(set-stack (list (get-preview)))
(reset! step-idx 0)
(for-each (fn (_) (do-step)) (slice (deref steps) 0 target)))))))
(for-each (fn (_) (do-step)) (slice (deref steps) 0 target))
(local-storage-set "sx-home-stepper" (freeze-to-sx "home-stepper")))))))
;; Freeze scope for persistence
(freeze-scope "home-stepper" (fn ()
(freeze-signal "step" step-idx)))
;; Restore from localStorage on mount
(let ((saved (local-storage-get "sx-home-stepper")))
(when saved
(thaw-from-sx saved)
;; Validate — reset to default if out of range
(when (or (< (deref step-idx) 0) (> (deref step-idx) 16))
(reset! step-idx 9))))
;; Auto-parse via effect
(effect (fn ()
(let ((parsed (sx-parse source)))
@@ -198,7 +210,9 @@
;; Defer code DOM build until lake exists
(schedule-idle (fn ()
(build-code-dom)
;; Replay to initial step-idx
;; Clear preview and replay to initial step-idx
(let ((preview (get-preview)))
(when preview (dom-set-prop preview "innerHTML" "")))
(let ((target (deref step-idx)))
(reset! step-idx 0)
(set-stack (list (get-preview)))
@@ -213,7 +227,7 @@
;; Controls
(div :class "flex items-center justify-center gap-2 md:gap-3"
(button :on-click (fn (e) (do-back))
:class (str "px-2 py-1 rounded text-lg "
:class (str "px-2 py-1 rounded text-3xl "
(if (> (deref step-idx) 0)
"text-stone-600 hover:text-stone-800 hover:bg-stone-100"
"text-stone-300 cursor-not-allowed"))
@@ -221,7 +235,7 @@
(span :class "text-sm text-stone-500 font-mono tabular-nums"
(deref step-idx) " / " (len (deref steps)))
(button :on-click (fn (e) (do-step))
:class (str "px-2 py-1 rounded text-lg "
:class (str "px-2 py-1 rounded text-3xl "
(if (< (deref step-idx) (len (deref steps)))
"text-violet-600 hover:text-violet-800 hover:bg-violet-50"
"text-violet-300 cursor-not-allowed"))

View File

@@ -174,7 +174,11 @@
(dict :label "Overview" :href "/sx/(geography.(cek))"
:summary "The CEK machine — explicit evaluator with Control, Environment, Kontinuation. Three registers, pure step function.")
(dict :label "Demo" :href "/sx/(geography.(cek.demo))"
:summary "Live islands evaluated by the CEK machine. Counter, computed chains, reactive attributes — all through explicit continuation frames.")))
:summary "Live islands evaluated by the CEK machine. Counter, computed chains, reactive attributes — all through explicit continuation frames.")
(dict :label "Freeze / Thaw" :href "/sx/(geography.(cek.freeze))"
:summary "Serialize a CEK state to s-expressions. Ship it, store it, content-address it. Thaw and resume anywhere.")
(dict :label "Content Addressing" :href "/sx/(geography.(cek.content))"
:summary "Hash frozen state to a CID. Same state = same address. Store, share, verify, reproduce.")))
(define plans-nav-items (list
(dict :label "Status" :href "/sx/(etc.(plan.status))"

View File

@@ -66,6 +66,8 @@
'(~geography/cek/cek-content)
(case slug
"demo" '(~geography/cek/cek-demo-content)
"freeze" '(~geography/cek/cek-freeze-content)
"content" '(~geography/cek/cek-content-address-content)
:else '(~geography/cek/cek-content)))))
(define provide