|
1 | 1 | // SPDX-License-Identifier: MPL-2.0 |
2 | | -// Example: Using SafeDOM for formally verified DOM mounting |
| 2 | +// SafeDOMExample.affine — formally-verified DOM mounting (aspirational). |
| 3 | +// |
| 4 | +// This example shows the *shape* of SafeDOM consumer code in current |
| 5 | +// AffineScript syntax. The `SafeDOM` stdlib surface it references |
| 6 | +// (`mount_safe`, `mount_when_ready`, `mount_batch`, |
| 7 | +// `proven_selector_validate`, `proven_html_validate`, `mount`) is the |
| 8 | +// target of `affinescript#56` (DOM+Pixi binding survey) and does not |
| 9 | +// yet exist in the published stdlib. The file is therefore |
| 10 | +// parse-checked but not type-checked end-to-end until #56 lands the |
| 11 | +// bindings; `affinescript check` reports `Resolve.UndefinedModule |
| 12 | +// SafeDOM` which is expected. |
| 13 | +// |
| 14 | +// Previous versions of this file (estate-wide, 5 dialect variants) |
| 15 | +// pre-dated ADR-014 (qualified paths), ADR-016 (effect rows), and the |
| 16 | +// `#{`-record-literal sigil (ADR-215). They were retired in favour of |
| 17 | +// this canonical via the gitbot-fleet#208 sweep (2026-05-26). |
| 18 | + |
| 19 | +module SafeDOMExample; |
| 20 | + |
| 21 | +use prelude::{Option, Some, None, Result, Ok, Err}; |
| 22 | + |
| 23 | +// `Element` and friends are nominal extern types for now — the real |
| 24 | +// shape lands with affinescript#56. |
| 25 | +extern type Element; |
| 26 | +extern type Selector; |
| 27 | +extern type ValidHTML; |
| 28 | + |
| 29 | +// Single-mount status, lifted from the host into a typed tag union. |
| 30 | +enum MountStatus { |
| 31 | + Mounted(Element), |
| 32 | + MountPointNotFound(String), |
| 33 | + InvalidSelector(String), |
| 34 | + InvalidHTML(String) |
| 35 | +} |
| 36 | + |
| 37 | +// Batch-mount result. |
| 38 | +enum MountResult { |
| 39 | + Mounted([Element]), |
| 40 | + Failed(String) |
| 41 | +} |
3 | 42 |
|
4 | | -import SafeDOM |
| 43 | +// Spec for one element in a batch mount. |
| 44 | +struct MountSpec { |
| 45 | + selector: String, |
| 46 | + html: String |
| 47 | +} |
5 | 48 |
|
6 | | -// Example 1: Basic mounting with error handling |
7 | | -fn mount_app() { |
8 | | - SafeDOM::mount_safe( |
| 49 | +// SafeDOM's host-side surface, all IO-effecting. Callbacks are passed |
| 50 | +// as separate parameters (rather than a `MountCallbacks` record) |
| 51 | +// because fn-typed struct fields are not currently parser-supported. |
| 52 | +extern fn mount_safe( |
| 53 | + selector: ref String, |
| 54 | + html: ref String, |
| 55 | + on_success: fn(Element) -> (), |
| 56 | + on_error: fn(String) -> (), |
| 57 | +) -{IO}-> (); |
| 58 | + |
| 59 | +extern fn mount_when_ready( |
| 60 | + selector: ref String, |
| 61 | + html: ref String, |
| 62 | + on_success: fn(Element) -> (), |
| 63 | + on_error: fn(String) -> (), |
| 64 | +) -{IO}-> (); |
| 65 | + |
| 66 | +extern fn mount_batch(specs: ref [MountSpec]) -{IO}-> MountResult; |
| 67 | + |
| 68 | +extern fn proven_selector_validate(s: ref String) -{IO}-> Result<Selector, String>; |
| 69 | +extern fn proven_html_validate(s: ref String) -{IO}-> Result<ValidHTML, String>; |
| 70 | +extern fn mount(sel: ref Selector, html: ref ValidHTML) -{IO}-> MountStatus; |
| 71 | + |
| 72 | +extern fn array_for_each(xs: ref [Element], f: fn(Element) -> ()) -{IO}-> (); |
| 73 | +extern fn array_len(xs: ref [Element]) -> Int; |
| 74 | + |
| 75 | +// Example 1 — basic mount with success/error branches. |
| 76 | +pub fn mount_app() -{IO}-> () { |
| 77 | + mount_safe( |
9 | 78 | "#app", |
10 | 79 | "<div><h1>Hello, World!</h1><p>Mounted safely with proofs.</p></div>", |
11 | | - on_success: fn(el) { |
12 | | - Console::log("✓ App mounted successfully!") |
13 | | - Console::log("Element: ", el) |
14 | | - }, |
15 | | - on_error: fn(err) { |
16 | | - Console::error("✗ Mount failed: ", err) |
17 | | - } |
18 | | - ) |
| 80 | + fn(el) -> () { Console::log("App mounted successfully"); }, |
| 81 | + fn(err) -> () { Console::error("Mount failed: " ++ err); }, |
| 82 | + ); |
19 | 83 | } |
20 | 84 |
|
21 | | -// Example 2: Wait for DOM ready before mounting |
22 | | -fn mount_when_dom_ready() { |
23 | | - SafeDOM::mount_when_ready( |
| 85 | +// Example 2 — defer until DOM ready. |
| 86 | +pub fn mount_when_dom_ready() -{IO}-> () { |
| 87 | + mount_when_ready( |
24 | 88 | "#app", |
25 | 89 | "<div class='container'><h1>App Title</h1></div>", |
26 | | - on_success: fn(_) { Console::log("✓ Mounted after DOM ready") }, |
27 | | - on_error: fn(err) { Console::error("✗ Failed: ", err) } |
28 | | - ) |
| 90 | + fn(_el) -> () { Console::log("Mounted after DOM ready"); }, |
| 91 | + fn(err) -> () { Console::error("Failed: " ++ err); }, |
| 92 | + ); |
29 | 93 | } |
30 | 94 |
|
31 | | -// Example 3: Batch mounting (atomic - all or nothing) |
32 | | -fn mount_multiple() { |
| 95 | +// Example 3 — atomic batch mount. |
| 96 | +pub fn mount_multiple() -{IO}-> () { |
33 | 97 | let specs = [ |
34 | | - {selector: "#header", html: "<header><h1>Site Title</h1></header>"}, |
35 | | - {selector: "#nav", html: "<nav><a href='/'>Home</a></nav>"}, |
36 | | - {selector: "#main", html: "<main><p>Content here</p></main>"}, |
37 | | - {selector: "#footer", html: "<footer>© 2026</footer>"} |
38 | | - ] |
39 | | - |
40 | | - match SafeDOM::mount_batch(specs) { |
41 | | - Ok(elements) => { |
42 | | - Console::log("✓ Successfully mounted ", len(elements), " elements") |
43 | | - for el in elements { |
44 | | - Console::log(" -", el) |
45 | | - } |
46 | | - } |
47 | | - Error(err) => { |
48 | | - Console::error("✗ Batch mount failed: ", err) |
49 | | - Console::error(" (None were mounted - atomic operation)") |
50 | | - } |
51 | | - } |
52 | | -} |
53 | | - |
54 | | -// Example 4: Explicit validation before mounting |
55 | | -fn mount_with_validation() { |
56 | | - // Validate selector first |
57 | | - match ProvenSelector::validate("#my-app") { |
58 | | - Error(e) => Console::error("Invalid selector: ", e) |
59 | | - Ok(valid_selector) => { |
60 | | - // Validate HTML |
61 | | - match ProvenHTML::validate("<div>Content</div>") { |
62 | | - Error(e) => Console::error("Invalid HTML: ", e) |
63 | | - Ok(valid_html) => { |
64 | | - // Now mount with proven safety |
65 | | - match SafeDOM::mount(valid_selector, valid_html) { |
66 | | - Mounted(el) => Console::log("✓ Mounted with validated inputs: ", el) |
67 | | - MountPointNotFound(s) => Console::error("✗ Element not found: ", s) |
68 | | - InvalidSelector(_) => Console::error("Impossible - already validated") |
69 | | - InvalidHTML(_) => Console::error("Impossible - already validated") |
70 | | - } |
71 | | - } |
72 | | - } |
| 98 | + MountSpec #{ selector: "#header", html: "<header><h1>Site Title</h1></header>" }, |
| 99 | + MountSpec #{ selector: "#nav", html: "<nav><a href='/'>Home</a></nav>" }, |
| 100 | + MountSpec #{ selector: "#main", html: "<main><p>Content here</p></main>" }, |
| 101 | + MountSpec #{ selector: "#footer", html: "<footer>2026</footer>" }, |
| 102 | + ]; |
| 103 | + |
| 104 | + match mount_batch(specs) { |
| 105 | + Mounted(elements) => { |
| 106 | + Console::log("Batch mount succeeded"); |
| 107 | + array_for_each(elements, fn(_el) -> () { Console::log(" element"); }); |
| 108 | + }, |
| 109 | + Failed(err) => { |
| 110 | + Console::error("Batch mount failed (atomic — none mounted): " ++ err); |
73 | 111 | } |
74 | 112 | } |
75 | 113 | } |
76 | 114 |
|
77 | | -// Example 5: Integration with TEA |
78 | | -namespace MyApp { |
79 | | - struct Model { |
80 | | - message: String |
81 | | - } |
82 | | - |
83 | | - enum Msg { |
84 | | - NoOp |
85 | | - } |
86 | | - |
87 | | - fn init() -> Model { |
88 | | - Model{message: "Hello from TEA"} |
89 | | - } |
90 | | - |
91 | | - fn update(_model: Model, _msg: Msg) -> Model { |
92 | | - _model |
93 | | - } |
94 | | - |
95 | | - fn view(model: Model) -> String { |
96 | | - "<div><h1>" + model.message + "</h1></div>" |
97 | | - } |
98 | | -} |
99 | | - |
100 | | -fn mount_tea_app() { |
101 | | - let model = MyApp::init() |
102 | | - let html = MyApp::view(model) |
103 | | - |
104 | | - SafeDOM::mount_when_ready( |
105 | | - "#tea-app", |
106 | | - html, |
107 | | - on_success: fn(el) { |
108 | | - Console::log("✓ TEA app mounted") |
109 | | - // Set up event handlers, subscriptions here |
| 115 | +// Example 4 — explicit two-stage validation before mounting. |
| 116 | +pub fn mount_with_validation() -{IO}-> () { |
| 117 | + match proven_selector_validate("#my-app") { |
| 118 | + Err(e) => Console::error("Invalid selector: " ++ e), |
| 119 | + Ok(valid_selector) => match proven_html_validate("<div>Content</div>") { |
| 120 | + Err(e) => Console::error("Invalid HTML: " ++ e), |
| 121 | + Ok(valid_html) => match mount(valid_selector, valid_html) { |
| 122 | + Mounted(_el) => Console::log("Mounted with validated inputs"), |
| 123 | + MountPointNotFound(s) => Console::error("Element not found: " ++ s), |
| 124 | + InvalidSelector(_) => Console::error("impossible — already validated"), |
| 125 | + InvalidHTML(_) => Console::error("impossible — already validated"), |
| 126 | + }, |
110 | 127 | }, |
111 | | - on_error: fn(err) { Console::error("✗ TEA mount failed: ", err) } |
112 | | - ) |
113 | | -} |
114 | | - |
115 | | -// Entry point |
116 | | -fn main() { |
117 | | - Console::log("SafeDOM Examples") |
118 | | - Console::log("================\n") |
119 | | - |
120 | | - // Choose which example to run |
121 | | - mount_when_dom_ready() // Run on DOM ready |
| 128 | + } |
122 | 129 | } |
123 | | - |
124 | | -// Auto-execute when module loads |
125 | | -main() |
|
0 commit comments