Move spec metadata from Python to SX, add orchestration to spec viewer
Spec file registry (slugs, filenames, titles, descriptions) now lives in nav-data.sx as SX data definitions. Python helper reduced to pure file I/O (read-spec-file). Architecture page updated with engine/orchestration split and dependency graph. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -78,7 +78,32 @@
|
|||||||
(dict :label "DOM Adapter" :href "/specs/adapter-dom")
|
(dict :label "DOM Adapter" :href "/specs/adapter-dom")
|
||||||
(dict :label "HTML Adapter" :href "/specs/adapter-html")
|
(dict :label "HTML Adapter" :href "/specs/adapter-html")
|
||||||
(dict :label "SX Wire Adapter" :href "/specs/adapter-sx")
|
(dict :label "SX Wire Adapter" :href "/specs/adapter-sx")
|
||||||
(dict :label "SxEngine" :href "/specs/engine")))
|
(dict :label "SxEngine" :href "/specs/engine")
|
||||||
|
(dict :label "Orchestration" :href "/specs/orchestration")))
|
||||||
|
|
||||||
|
;; Spec file registry — canonical metadata for spec viewer pages.
|
||||||
|
;; Python only handles file I/O (read-spec-file); all metadata lives here.
|
||||||
|
|
||||||
|
(define core-spec-items (list
|
||||||
|
(dict :slug "parser" :filename "parser.sx" :title "Parser" :desc "Tokenization and parsing of SX source text into AST.")
|
||||||
|
(dict :slug "evaluator" :filename "eval.sx" :title "Evaluator" :desc "Tree-walking evaluation of SX expressions.")
|
||||||
|
(dict :slug "primitives" :filename "primitives.sx" :title "Primitives" :desc "All built-in pure functions and their signatures.")
|
||||||
|
(dict :slug "renderer" :filename "render.sx" :title "Renderer" :desc "Shared rendering registries and utilities used by all adapters.")))
|
||||||
|
|
||||||
|
(define adapter-spec-items (list
|
||||||
|
(dict :slug "adapter-dom" :filename "adapter-dom.sx" :title "DOM Adapter" :desc "Renders SX expressions to live DOM nodes. Browser-only.")
|
||||||
|
(dict :slug "adapter-html" :filename "adapter-html.sx" :title "HTML Adapter" :desc "Renders SX expressions to HTML strings. Server-side.")
|
||||||
|
(dict :slug "adapter-sx" :filename "adapter-sx.sx" :title "SX Wire Adapter" :desc "Serializes SX for client-side rendering. Component calls stay unexpanded.")
|
||||||
|
(dict :slug "engine" :filename "engine.sx" :title "SxEngine" :desc "Pure logic for fetch, swap, history, SSE, triggers, morph, and indicators.")
|
||||||
|
(dict :slug "orchestration" :filename "orchestration.sx" :title "Orchestration" :desc "Browser wiring that binds engine logic to DOM events, fetch, and lifecycle.")))
|
||||||
|
|
||||||
|
(define all-spec-items (concat core-spec-items adapter-spec-items))
|
||||||
|
|
||||||
|
(define find-spec
|
||||||
|
(fn (slug)
|
||||||
|
(some (fn (item)
|
||||||
|
(when (= (get item "slug") slug) item))
|
||||||
|
all-spec-items)))
|
||||||
|
|
||||||
;; Find the current nav label for a slug by matching href suffix.
|
;; Find the current nav label for a slug by matching href suffix.
|
||||||
;; Returns the label string or nil if no match.
|
;; Returns the label string or nil if no match.
|
||||||
|
|||||||
@@ -96,7 +96,11 @@
|
|||||||
(p :class "text-stone-600"
|
(p :class "text-stone-600"
|
||||||
"The engine is the browser-side fetch/swap/history system. It processes "
|
"The engine is the browser-side fetch/swap/history system. It processes "
|
||||||
(code :class "text-violet-700 text-sm" "sx-*")
|
(code :class "text-violet-700 text-sm" "sx-*")
|
||||||
" attributes on elements to make HTTP requests, swap content, manage browser history, and handle events. It depends on the core evaluator and the DOM adapter.")
|
" attributes on elements to make HTTP requests, swap content, manage browser history, and handle events. It is split into two files: pure logic ("
|
||||||
|
(code :class "text-violet-700 text-sm" "engine.sx")
|
||||||
|
") and browser wiring ("
|
||||||
|
(code :class "text-violet-700 text-sm" "orchestration.sx")
|
||||||
|
").")
|
||||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||||
(table :class "w-full text-left text-sm"
|
(table :class "w-full text-left text-sm"
|
||||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||||
@@ -109,7 +113,14 @@
|
|||||||
:sx-get "/specs/engine" :sx-target "#main-panel" :sx-select "#main-panel"
|
:sx-get "/specs/engine" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||||
:sx-swap "outerHTML" :sx-push-url "true"
|
:sx-swap "outerHTML" :sx-push-url "true"
|
||||||
"engine.sx"))
|
"engine.sx"))
|
||||||
(td :class "px-3 py-2 text-stone-700" "SxEngine — fetch, swap, history, SSE, triggers, indicators"))))))
|
(td :class "px-3 py-2 text-stone-700" "Pure logic — trigger parsing, swap algorithms, morph, history, SSE, indicators"))
|
||||||
|
(tr :class "border-b border-stone-100"
|
||||||
|
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
|
||||||
|
(a :href "/specs/orchestration" :class "hover:underline"
|
||||||
|
:sx-get "/specs/orchestration" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||||
|
:sx-swap "outerHTML" :sx-push-url "true"
|
||||||
|
"orchestration.sx"))
|
||||||
|
(td :class "px-3 py-2 text-stone-700" "Browser wiring — binds engine to DOM events, fetch, request lifecycle"))))))
|
||||||
|
|
||||||
(div :class "space-y-3"
|
(div :class "space-y-3"
|
||||||
(h2 :class "text-2xl font-semibold text-stone-800" "Dependency graph")
|
(h2 :class "text-2xl font-semibold text-stone-800" "Dependency graph")
|
||||||
@@ -124,7 +135,8 @@ adapter-dom.sx depends on: render, eval
|
|||||||
adapter-html.sx depends on: render, eval
|
adapter-html.sx depends on: render, eval
|
||||||
adapter-sx.sx depends on: render, eval
|
adapter-sx.sx depends on: render, eval
|
||||||
|
|
||||||
engine.sx depends on: eval, adapter-dom")))
|
engine.sx depends on: eval, adapter-dom
|
||||||
|
orchestration.sx depends on: engine, adapter-dom")))
|
||||||
|
|
||||||
(div :class "space-y-3"
|
(div :class "space-y-3"
|
||||||
(h2 :class "text-2xl font-semibold text-stone-800" "Self-hosting")
|
(h2 :class "text-2xl font-semibold text-stone-800" "Self-hosting")
|
||||||
@@ -143,14 +155,14 @@ engine.sx depends on: eval, adapter-dom")))
|
|||||||
;; Overview pages (Core / Adapters) — show truncated previews of each file
|
;; Overview pages (Core / Adapters) — show truncated previews of each file
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
(defcomp ~spec-overview-content (&key spec-files)
|
(defcomp ~spec-overview-content (&key spec-title spec-files)
|
||||||
(~doc-page :title (or spec-title "Specs")
|
(~doc-page :title (or spec-title "Specs")
|
||||||
(p :class "text-stone-600 mb-6"
|
(p :class "text-stone-600 mb-6"
|
||||||
(case spec-title
|
(case spec-title
|
||||||
"Core Language"
|
"Core Language"
|
||||||
"The core specification defines the language itself — parsing, evaluation, primitives, and shared rendering definitions. These four files are platform-independent and sufficient to implement SX on any target."
|
"The core specification defines the language itself — parsing, evaluation, primitives, and shared rendering definitions. These four files are platform-independent and sufficient to implement SX on any target."
|
||||||
"Adapters & Engine"
|
"Adapters & Engine"
|
||||||
"Adapters connect the core language to specific environments. Each adapter takes evaluated expression trees and produces output for its target. The engine adds browser-side fetch/swap behaviour."
|
"Adapters connect the core language to specific environments. Each adapter takes evaluated expression trees and produces output for its target. The engine adds browser-side fetch/swap behaviour, split into pure logic and browser orchestration."
|
||||||
:else ""))
|
:else ""))
|
||||||
(div :class "space-y-8"
|
(div :class "space-y-8"
|
||||||
(map (fn (spec)
|
(map (fn (spec)
|
||||||
|
|||||||
@@ -268,14 +268,26 @@
|
|||||||
:sub-nav (~section-nav :items specs-nav-items
|
:sub-nav (~section-nav :items specs-nav-items
|
||||||
:current (find-current specs-nav-items slug))
|
:current (find-current specs-nav-items slug))
|
||||||
:selected (or (find-current specs-nav-items slug) ""))
|
:selected (or (find-current specs-nav-items slug) ""))
|
||||||
:data (spec-data slug)
|
:content (case slug
|
||||||
:content (if spec-not-found
|
"core" (~spec-overview-content
|
||||||
(~spec-not-found :slug slug)
|
:spec-title "Core Language"
|
||||||
(case slug
|
:spec-files (map (fn (item)
|
||||||
"core" (~spec-overview-content :spec-files spec-files)
|
(dict :title (get item "title") :desc (get item "desc")
|
||||||
"adapters" (~spec-overview-content :spec-files spec-files)
|
:filename (get item "filename") :href (str "/specs/" (get item "slug"))
|
||||||
:else (~spec-detail-content
|
:source (read-spec-file (get item "filename"))))
|
||||||
:spec-title spec-title
|
core-spec-items))
|
||||||
:spec-desc spec-desc
|
"adapters" (~spec-overview-content
|
||||||
:spec-filename spec-filename
|
:spec-title "Adapters & Engine"
|
||||||
:spec-source spec-source))))
|
:spec-files (map (fn (item)
|
||||||
|
(dict :title (get item "title") :desc (get item "desc")
|
||||||
|
:filename (get item "filename") :href (str "/specs/" (get item "slug"))
|
||||||
|
:source (read-spec-file (get item "filename"))))
|
||||||
|
adapter-spec-items))
|
||||||
|
:else (let ((spec (find-spec slug)))
|
||||||
|
(if spec
|
||||||
|
(~spec-detail-content
|
||||||
|
:spec-title (get spec "title")
|
||||||
|
:spec-desc (get spec "desc")
|
||||||
|
:spec-filename (get spec "filename")
|
||||||
|
:spec-source (read-spec-file (get spec "filename")))
|
||||||
|
(~spec-not-found :slug slug)))))
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ def _register_sx_helpers() -> None:
|
|||||||
"primitives-data": _primitives_data,
|
"primitives-data": _primitives_data,
|
||||||
"reference-data": _reference_data,
|
"reference-data": _reference_data,
|
||||||
"attr-detail-data": _attr_detail_data,
|
"attr-detail-data": _attr_detail_data,
|
||||||
"spec-data": _spec_data,
|
"read-spec-file": _read_spec_file,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -104,72 +104,13 @@ def _reference_data(slug: str) -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_CORE_SPECS = {
|
def _read_spec_file(filename: str) -> str:
|
||||||
"parser": ("parser.sx", "Parser", "Tokenization and parsing of SX source text into AST."),
|
"""Read a spec .sx file from the ref directory. Pure I/O — metadata lives in .sx."""
|
||||||
"evaluator": ("eval.sx", "Evaluator", "Tree-walking evaluation of SX expressions."),
|
|
||||||
"primitives": ("primitives.sx", "Primitives", "All built-in pure functions and their signatures."),
|
|
||||||
"renderer": ("render.sx", "Renderer", "Shared rendering registries and utilities used by all adapters."),
|
|
||||||
}
|
|
||||||
|
|
||||||
_ADAPTER_SPECS = {
|
|
||||||
"adapter-dom": ("adapter-dom.sx", "DOM Adapter", "Renders SX expressions to live DOM nodes. Browser-only."),
|
|
||||||
"adapter-html": ("adapter-html.sx", "HTML Adapter", "Renders SX expressions to HTML strings. Server-side."),
|
|
||||||
"adapter-sx": ("adapter-sx.sx", "SX Wire Adapter", "Serializes SX for client-side rendering. Component calls stay unexpanded."),
|
|
||||||
"engine": ("engine.sx", "SxEngine", "Fetch/swap/history engine for browser-side SX. Like HTMX but native to SX."),
|
|
||||||
}
|
|
||||||
|
|
||||||
_ALL_SPECS = {**_CORE_SPECS, **_ADAPTER_SPECS}
|
|
||||||
|
|
||||||
|
|
||||||
def _spec_data(slug: str) -> dict:
|
|
||||||
"""Return spec file source and metadata for display."""
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
ref_dir = os.path.join(os.path.dirname(__file__), "..", "..", "shared", "sx", "ref")
|
ref_dir = os.path.join(os.path.dirname(__file__), "..", "..", "shared", "sx", "ref")
|
||||||
if not os.path.isdir(ref_dir):
|
if not os.path.isdir(ref_dir):
|
||||||
ref_dir = "/app/shared/sx/ref"
|
ref_dir = "/app/shared/sx/ref"
|
||||||
|
|
||||||
base = {"spec-not-found": None, "spec-title": None, "spec-desc": None,
|
|
||||||
"spec-filename": None, "spec-source": None, "spec-files": None}
|
|
||||||
|
|
||||||
if slug == "core":
|
|
||||||
specs = []
|
|
||||||
for key in ("parser", "evaluator", "primitives", "renderer"):
|
|
||||||
filename, title, desc = _CORE_SPECS[key]
|
|
||||||
filepath = os.path.join(ref_dir, filename)
|
|
||||||
source = _read_spec(filepath)
|
|
||||||
specs.append({
|
|
||||||
"title": title, "desc": desc, "filename": filename,
|
|
||||||
"source": source, "href": f"/specs/{key}",
|
|
||||||
})
|
|
||||||
return {**base, "spec-title": "Core Language", "spec-files": specs}
|
|
||||||
|
|
||||||
if slug == "adapters":
|
|
||||||
specs = []
|
|
||||||
for key in ("adapter-dom", "adapter-html", "adapter-sx", "engine"):
|
|
||||||
filename, title, desc = _ADAPTER_SPECS[key]
|
|
||||||
filepath = os.path.join(ref_dir, filename)
|
|
||||||
source = _read_spec(filepath)
|
|
||||||
specs.append({
|
|
||||||
"title": title, "desc": desc, "filename": filename,
|
|
||||||
"source": source, "href": f"/specs/{key}",
|
|
||||||
})
|
|
||||||
return {**base, "spec-title": "Adapters & Engine", "spec-files": specs}
|
|
||||||
|
|
||||||
info = _ALL_SPECS.get(slug)
|
|
||||||
if not info:
|
|
||||||
return {**base, "spec-not-found": True}
|
|
||||||
|
|
||||||
filename, title, desc = info
|
|
||||||
filepath = os.path.join(ref_dir, filename)
|
filepath = os.path.join(ref_dir, filename)
|
||||||
source = _read_spec(filepath)
|
|
||||||
return {**base,
|
|
||||||
"spec-title": title, "spec-desc": desc,
|
|
||||||
"spec-filename": filename, "spec-source": source}
|
|
||||||
|
|
||||||
|
|
||||||
def _read_spec(filepath: str) -> str:
|
|
||||||
"""Read a spec file, returning empty string if missing."""
|
|
||||||
try:
|
try:
|
||||||
with open(filepath, encoding="utf-8") as f:
|
with open(filepath, encoding="utf-8") as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|||||||
Reference in New Issue
Block a user