From 3ec802b5cac458508e75559e53f8486b184ff4ec Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Tue, 26 May 2026 11:09:11 +0100 Subject: [PATCH] fix(examples): migrate SafeDOMExample.affine to current AffineScript grammar (Refs gitbot-fleet#148, #208) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the pre-stabilization-dialect copy with the current-grammar canonical (matches the byte-identical gitbot-fleet/bots/*/examples/ SafeDOMExample.affine landing in hyperpolymath/gitbot-fleet#210). The previous content pre-dated ADR-014 (qualified paths), ADR-016 (effect rows), and ADR-215 (`#{`-record-literal sigil), and parse-failed at line 1 of `affinescript check` everywhere it lived. The example references the `SafeDOM` stdlib surface targeted by affinescript#56 (DOM+Pixi binding survey) which does not yet exist; `affinescript check` therefore reports `Resolve.UndefinedModule SafeDOM` — the expected residual (parser layer succeeds). Refs hyperpolymath/gitbot-fleet#148 (sustainabot ReScript→AffineScript hand-port validation — same session) Refs hyperpolymath/gitbot-fleet#208 (estate-wide SafeDOMExample sweep tracker) Refs hyperpolymath/gitbot-fleet#210 (canonical lands; all 5 sweep PRs ship byte-identical content) Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/SafeDOMExample.affine | 201 ++++++++++++++++++--------------- 1 file changed, 109 insertions(+), 92 deletions(-) diff --git a/examples/SafeDOMExample.affine b/examples/SafeDOMExample.affine index fb97485..2a62c1d 100644 --- a/examples/SafeDOMExample.affine +++ b/examples/SafeDOMExample.affine @@ -1,112 +1,129 @@ // SPDX-License-Identifier: MPL-2.0 +// SafeDOMExample.affine — formally-verified DOM mounting (aspirational). // -// SafeDOMExample.affine — Using SafeDOM for formally verified DOM mounting. +// 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. // -// AffineScript migration of SafeDOMExample.res +// 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). -open SafeDOM +module SafeDOMExample; -// Example 1: Basic mounting with error handling -let mountApp = () => { - mountSafe( - "#app", - "

Hello, World!

Mounted safely with proofs.

", - ~onSuccess=el => { - Console.log("✓ App mounted successfully!") - Console.log("Element:", el) - }, - ~onError=err => { - Console.error("✗ Mount failed:", err) - } - ) -} +use prelude::{Option, Some, None, Result, Ok, Err}; -// Example 2: Wait for DOM ready before mounting -let mountWhenDOMReady = () => { - mountWhenReady( - "#app", - "

App Title

", - ~onSuccess=_ => Console.log("✓ Mounted after DOM ready"), - ~onError=err => Console.error("✗ Failed:", 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) } -// Example 3: Batch mounting (atomic - all or nothing) -let mountMultiple = () => { - let specs = [ - {selector: "#header", html: "

Site Title

"}, - {selector: "#nav", html: ""}, - {selector: "#main", html: "

Content here

"}, - {selector: "#footer", html: ""} - ] - - switch mountBatch(specs) { - | Ok(elements) => { - Console.log(`✓ Successfully mounted ${Array.length(elements)} elements`) - elements->Array.forEach(el => Console.log(" -", el)) - } - | Error(err) => { - Console.error("✗ Batch mount failed:", err) - Console.error(" (None were mounted - atomic operation)") - } - } +// Batch-mount result. +enum MountResult { + Mounted([Element]), + Failed(String) } -// Example 4: Explicit validation before mounting -let mountWithValidation = () => { - // Validate selector first - switch ProvenSelector.validate("#my-app") { - | Error(e) => Console.error(`Invalid selector: ${e}`) - | Ok(validSelector) => { - // Validate HTML - switch ProvenHTML.validate("
Content
") { - | Error(e) => Console.error(`Invalid HTML: ${e}`) - | Ok(validHtml) => { - // Now mount with proven safety - switch mount(validSelector, validHtml) { - | 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") - } - } - } - } +// Spec for one element in a batch mount. +struct MountSpec { + selector: String, + html: String } -// Example 5: Integration with TEA -module MyApp = { - type model = {message: string} - type msg = NoOp +// 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}-> (); - let init = () => {message: "Hello from TEA"} - let update = (model, _msg) => model - let view = model => `

${model.message}

` -} +extern fn mount_when_ready( + selector: ref String, + html: ref String, + on_success: fn(Element) -> (), + on_error: fn(String) -> (), +) -{IO}-> (); -let mountTEAApp = () => { - let model = MyApp.init() - let html = MyApp.view(model) +extern fn mount_batch(specs: ref [MountSpec]) -{IO}-> MountResult; - mountWhenReady( - "#tea-app", - html, - ~onSuccess=el => { - Console.log("✓ TEA app mounted") - // Set up event handlers, subscriptions here - }, - ~onError=err => Console.error(`✗ TEA mount failed: ${err}`) - ) +extern fn proven_selector_validate(s: ref String) -{IO}-> Result; +extern fn proven_html_validate(s: ref String) -{IO}-> Result; +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", + "

Hello, World!

Mounted safely with proofs.

", + fn(el) -> () { Console::log("App mounted successfully"); }, + fn(err) -> () { Console::error("Mount failed: " ++ err); }, + ); } -// Entry point -let main = () => { - Console.log("SafeDOM Examples") - Console.log("================\n") +// Example 2 — defer until DOM ready. +pub fn mount_when_dom_ready() -{IO}-> () { + mount_when_ready( + "#app", + "

App Title

", + fn(_el) -> () { Console::log("Mounted after DOM ready"); }, + fn(err) -> () { Console::error("Failed: " ++ err); }, + ); +} - // Choose which example to run - mountWhenDOMReady() // Run on DOM ready +// Example 3 — atomic batch mount. +pub fn mount_multiple() -{IO}-> () { + let specs = [ + MountSpec #{ selector: "#header", html: "

Site Title

" }, + MountSpec #{ selector: "#nav", html: "" }, + MountSpec #{ selector: "#main", html: "

Content here

" }, + MountSpec #{ selector: "#footer", html: "
2026
" }, + ]; + + 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); + } + } } -// Auto-execute when module loads -main() +// 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("
Content
") { + 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"), + }, + }, + } +}