From 2a9a4b41bd466d0c9081fc2a88110bfe1a3acac5 Mon Sep 17 00:00:00 2001 From: giles Date: Tue, 24 Mar 2026 10:06:05 +0000 Subject: [PATCH] =?UTF-8?q?Stable=20extension=20point=20for=20definition-f?= =?UTF-8?q?orm=3F=20=E2=80=94=20no=20monkey-patching?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the fragile pattern of capturing and wrapping definition-form? with a mutable *definition-form-extensions* list in render.sx. Web modules append names to this list instead of redefining the function. Survives spec reloads without losing registrations. Co-Authored-By: Claude Opus 4.6 (1M context) --- shared/static/scripts/sx-browser.js | 8 ++++++-- spec/render.sx | 7 ++++++- web/web-forms.sx | 21 ++++++++------------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index bd29fbf..c982b31 100644 --- a/shared/static/scripts/sx-browser.js +++ b/shared/static/scripts/sx-browser.js @@ -14,7 +14,7 @@ // ========================================================================= var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); - var SX_VERSION = "2026-03-24T09:53:22Z"; + var SX_VERSION = "2026-03-24T10:04:16Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -2236,8 +2236,12 @@ PRIMITIVES["VOID_ELEMENTS"] = VOID_ELEMENTS; var BOOLEAN_ATTRS = ["async", "autofocus", "autoplay", "checked", "controls", "default", "defer", "disabled", "formnovalidate", "hidden", "inert", "ismap", "loop", "multiple", "muted", "nomodule", "novalidate", "open", "playsinline", "readonly", "required", "reversed", "selected"]; PRIMITIVES["BOOLEAN_ATTRS"] = BOOLEAN_ATTRS; + // *definition-form-extensions* + var _definitionFormExtensions_ = []; +PRIMITIVES["*definition-form-extensions*"] = _definitionFormExtensions_; + // definition-form? - var isDefinitionForm = function(name) { return sxOr((name == "define"), (name == "defcomp"), (name == "defisland"), (name == "defmacro"), (name == "defstyle"), (name == "deftype"), (name == "defeffect")); }; + var isDefinitionForm = function(name) { return sxOr((name == "define"), (name == "defcomp"), (name == "defisland"), (name == "defmacro"), (name == "defstyle"), (name == "deftype"), (name == "defeffect"), contains(_definitionFormExtensions_, name)); }; PRIMITIVES["definition-form?"] = isDefinitionForm; // parse-element-args diff --git a/spec/render.sx b/spec/render.sx index 0ef9423..039c328 100644 --- a/spec/render.sx +++ b/spec/render.sx @@ -71,11 +71,16 @@ ;; Shared utilities ;; -------------------------------------------------------------------------- +;; Extension point for definition forms — modules append names here. +;; Survives spec reloads (no function wrapping needed). +(define *definition-form-extensions* (list)) + (define definition-form? :effects [] (fn ((name :as string)) (or (= name "define") (= name "defcomp") (= name "defisland") (= name "defmacro") (= name "defstyle") - (= name "deftype") (= name "defeffect")))) + (= name "deftype") (= name "defeffect") + (contains? *definition-form-extensions* name)))) (define parse-element-args :effects [render] diff --git a/web/web-forms.sx b/web/web-forms.sx index f876c1f..2bf24bb 100644 --- a/web/web-forms.sx +++ b/web/web-forms.sx @@ -213,27 +213,22 @@ ;; -------------------------------------------------------------------------- -;; Patch form-classification functions +;; Register web forms with adapters ;; -;; The adapters (html, sx, dom, async) use classifier functions to decide -;; how to handle forms during rendering. Now that these web forms are -;; registered as custom special forms, we redefine the classifiers to -;; include them. This runs after all adapters are loaded. +;; Appends form names to the extension lists that definition-form?, +;; render-html-form?, special-form? etc. check. No function wrapping — +;; survives spec reloads. ;; -------------------------------------------------------------------------- (define WEB_FORM_NAMES (list "defhandler" "defpage" "defquery" "defaction" "defrelation")) -;; Redefine definition-form? to include web forms. -;; All adapters call this to identify "eval for side effects" forms. -(let ((core-definition-form? definition-form?)) - (define definition-form? - (fn (name) - (or (core-definition-form? name) - (contains? WEB_FORM_NAMES name))))) +;; Extend definition-form? via the stable extension point in render.sx +(for-each (fn (name) + (append! *definition-form-extensions* name)) + WEB_FORM_NAMES) ;; Extend adapter form-name lists so dispatchers recognise web forms. -;; These lists are mutable — append! adds to them in place. (for-each (fn (name) (append! RENDER_HTML_FORMS name) (append! SPECIAL_FORM_NAMES name))