Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 111 additions & 107 deletions examples/SafeDOMExample.affine
Original file line number Diff line number Diff line change
@@ -1,125 +1,129 @@
// SPDX-License-Identifier: MPL-2.0
// Example: Using SafeDOM for formally verified DOM mounting
// SafeDOMExample.affine — formally-verified DOM mounting (aspirational).
//
// This example shows the *shape* of SafeDOM consumer code in current
// AffineScript syntax. The `SafeDOM` stdlib surface it references
// (`mount_safe`, `mount_when_ready`, `mount_batch`,
// `proven_selector_validate`, `proven_html_validate`, `mount`) is the
// target of `affinescript#56` (DOM+Pixi binding survey) and does not
// yet exist in the published stdlib. The file is therefore
// parse-checked but not type-checked end-to-end until #56 lands the
// bindings; `affinescript check` reports `Resolve.UndefinedModule
// SafeDOM` which is expected.
//
// Previous versions of this file (estate-wide, 5 dialect variants)
// pre-dated ADR-014 (qualified paths), ADR-016 (effect rows), and the
// `#{`-record-literal sigil (ADR-215). They were retired in favour of
// this canonical via the gitbot-fleet#208 sweep (2026-05-26).

module SafeDOMExample;

use prelude::{Option, Some, None, Result, Ok, Err};

// `Element` and friends are nominal extern types for now — the real
// shape lands with affinescript#56.
extern type Element;
extern type Selector;
extern type ValidHTML;

// Single-mount status, lifted from the host into a typed tag union.
enum MountStatus {
Mounted(Element),
MountPointNotFound(String),
InvalidSelector(String),
InvalidHTML(String)
}

// Batch-mount result.
enum MountResult {
Mounted([Element]),
Failed(String)
}

import SafeDOM
// Spec for one element in a batch mount.
struct MountSpec {
selector: String,
html: String
}

// Example 1: Basic mounting with error handling
fn mount_app() {
SafeDOM::mount_safe(
// SafeDOM's host-side surface, all IO-effecting. Callbacks are passed
// as separate parameters (rather than a `MountCallbacks` record)
// because fn-typed struct fields are not currently parser-supported.
extern fn mount_safe(
selector: ref String,
html: ref String,
on_success: fn(Element) -> (),
on_error: fn(String) -> (),
) -{IO}-> ();

extern fn mount_when_ready(
selector: ref String,
html: ref String,
on_success: fn(Element) -> (),
on_error: fn(String) -> (),
) -{IO}-> ();

extern fn mount_batch(specs: ref [MountSpec]) -{IO}-> MountResult;

extern fn proven_selector_validate(s: ref String) -{IO}-> Result<Selector, String>;
extern fn proven_html_validate(s: ref String) -{IO}-> Result<ValidHTML, String>;
extern fn mount(sel: ref Selector, html: ref ValidHTML) -{IO}-> MountStatus;

extern fn array_for_each(xs: ref [Element], f: fn(Element) -> ()) -{IO}-> ();
extern fn array_len(xs: ref [Element]) -> Int;

// Example 1 — basic mount with success/error branches.
pub fn mount_app() -{IO}-> () {
mount_safe(
"#app",
"<div><h1>Hello, World!</h1><p>Mounted safely with proofs.</p></div>",
on_success: fn(el) {
Console::log("✓ App mounted successfully!")
Console::log("Element: ", el)
},
on_error: fn(err) {
Console::error("✗ Mount failed: ", err)
}
)
fn(el) -> () { Console::log("App mounted successfully"); },
fn(err) -> () { Console::error("Mount failed: " ++ err); },
);
}

// Example 2: Wait for DOM ready before mounting
fn mount_when_dom_ready() {
SafeDOM::mount_when_ready(
// Example 2 — defer until DOM ready.
pub fn mount_when_dom_ready() -{IO}-> () {
mount_when_ready(
"#app",
"<div class='container'><h1>App Title</h1></div>",
on_success: fn(_) { Console::log("Mounted after DOM ready") },
on_error: fn(err) { Console::error("Failed: ", err) }
)
fn(_el) -> () { Console::log("Mounted after DOM ready"); },
fn(err) -> () { Console::error("Failed: " ++ err); },
);
}

// Example 3: Batch mounting (atomic - all or nothing)
fn mount_multiple() {
// Example 3atomic batch mount.
pub fn mount_multiple() -{IO}-> () {
let specs = [
{selector: "#header", html: "<header><h1>Site Title</h1></header>"},
{selector: "#nav", html: "<nav><a href='/'>Home</a></nav>"},
{selector: "#main", html: "<main><p>Content here</p></main>"},
{selector: "#footer", html: "<footer>© 2026</footer>"}
]

match SafeDOM::mount_batch(specs) {
Ok(elements) => {
Console::log("✓ Successfully mounted ", len(elements), " elements")
for el in elements {
Console::log(" -", el)
}
}
Error(err) => {
Console::error("✗ Batch mount failed: ", err)
Console::error(" (None were mounted - atomic operation)")
}
}
}

// Example 4: Explicit validation before mounting
fn mount_with_validation() {
// Validate selector first
match ProvenSelector::validate("#my-app") {
Error(e) => Console::error("Invalid selector: ", e)
Ok(valid_selector) => {
// Validate HTML
match ProvenHTML::validate("<div>Content</div>") {
Error(e) => Console::error("Invalid HTML: ", e)
Ok(valid_html) => {
// Now mount with proven safety
match SafeDOM::mount(valid_selector, valid_html) {
Mounted(el) => Console::log("✓ Mounted with validated inputs: ", el)
MountPointNotFound(s) => Console::error("✗ Element not found: ", s)
InvalidSelector(_) => Console::error("Impossible - already validated")
InvalidHTML(_) => Console::error("Impossible - already validated")
}
}
}
MountSpec #{ selector: "#header", html: "<header><h1>Site Title</h1></header>" },
MountSpec #{ selector: "#nav", html: "<nav><a href='/'>Home</a></nav>" },
MountSpec #{ selector: "#main", html: "<main><p>Content here</p></main>" },
MountSpec #{ selector: "#footer", html: "<footer>2026</footer>" },
];

match mount_batch(specs) {
Mounted(elements) => {
Console::log("Batch mount succeeded");
array_for_each(elements, fn(_el) -> () { Console::log(" element"); });
},
Failed(err) => {
Console::error("Batch mount failed (atomic — none mounted): " ++ err);
}
}
}

// Example 5: Integration with TEA
namespace MyApp {
struct Model {
message: String
}

enum Msg {
NoOp
}

fn init() -> Model {
Model{message: "Hello from TEA"}
}

fn update(_model: Model, _msg: Msg) -> Model {
_model
}

fn view(model: Model) -> String {
"<div><h1>" + model.message + "</h1></div>"
}
}

fn mount_tea_app() {
let model = MyApp::init()
let html = MyApp::view(model)

SafeDOM::mount_when_ready(
"#tea-app",
html,
on_success: fn(el) {
Console::log("✓ TEA app mounted")
// Set up event handlers, subscriptions here
// Example 4 — explicit two-stage validation before mounting.
pub fn mount_with_validation() -{IO}-> () {
match proven_selector_validate("#my-app") {
Err(e) => Console::error("Invalid selector: " ++ e),
Ok(valid_selector) => match proven_html_validate("<div>Content</div>") {
Err(e) => Console::error("Invalid HTML: " ++ e),
Ok(valid_html) => match mount(valid_selector, valid_html) {
Mounted(_el) => Console::log("Mounted with validated inputs"),
MountPointNotFound(s) => Console::error("Element not found: " ++ s),
InvalidSelector(_) => Console::error("impossible — already validated"),
InvalidHTML(_) => Console::error("impossible — already validated"),
},
},
on_error: fn(err) { Console::error("✗ TEA mount failed: ", err) }
)
}

// Entry point
fn main() {
Console::log("SafeDOM Examples")
Console::log("================\n")

// Choose which example to run
mount_when_dom_ready() // Run on DOM ready
}
}

// Auto-execute when module loads
main()
Loading