Adopt Step 7 language features across SX codebase

112 conversions across 19 .sx files using match, let-match, and pipe operators:

match (17): type/value dispatch replacing cond/if chains
  - lib/vm.sx: HO form dispatch (for-each/map/filter/reduce/some/every?)
  - lib/tree-tools.sx: node-display, node-matches?, rename, count, replace, free-symbols
  - lib/types.sx: narrow-type, substitute-in-type, infer-type, resolve-type
  - web/engine.sx: default-trigger, resolve-target, classify-trigger
  - web/deps.sx: scan-refs-walk, scan-io-refs-walk

let-match (89): dict destructuring replacing (get d "key") patterns
  - shared/page-functions.sx (20), blog/admin.sx (17), pub-api.sx (13)
  - events/ layouts/page/tickets/entries/forms (27 total)
  - specs-explorer.sx (7), federation/social.sx (3), lib/ small files (3)

-> pipes (6): replacing triple-chained gets in lib/vm.sx
  - frame-closure → closure-code → code-bytecode chains

Also: lib/vm.sx accessor upgrades (get vm "sp" → vm-sp vm throughout)

2650/2650 tests pass, zero regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 20:49:02 +00:00
parent aee4770a6a
commit c0665ba58e
19 changed files with 4974 additions and 3771 deletions

View File

@@ -98,24 +98,47 @@
(~page/slot-description-oob :description (or description "")))))
;; Slots table from data
(defcomp ~page/slots-table-from-data (&key list-container slots pre-action add-url
tr-cls pill-cls action-btn hx-select csrf-hdr)
(defcomp
~page/slots-table-from-data
(&key
list-container
slots
pre-action
add-url
tr-cls
pill-cls
action-btn
hx-select
csrf-hdr)
(~page/slots-table
:list-container list-container
:rows (if (empty? (or slots (list)))
:rows (if
(empty? (or slots (list)))
(~page/slots-empty-row)
(<> (map (lambda (s)
(~page/slots-row
:tr-cls tr-cls :slot-href (get s "slot-href")
:pill-cls pill-cls :hx-select hx-select
:slot-name (get s "slot-name") :description (get s "description")
:flexible (get s "flexible")
:days (~page/days-pills-from-data :days (get s "days"))
:time-str (get s "time-str")
:cost-str (get s "cost-str") :action-btn action-btn
:del-url (get s "del-url") :csrf-hdr csrf-hdr))
(or slots (list)))))
:pre-action pre-action :add-url add-url))
(<>
(map
(lambda
(s)
(let-match
{:slot-name slot-name :time-str time-str :flexible flexible :description description :days days :cost-str cost-str :del-url del-url :slot-href slot-href}
s
(~page/slots-row
:tr-cls tr-cls
:slot-href slot-href
:pill-cls pill-cls
:hx-select hx-select
:slot-name slot-name
:description description
:flexible flexible
:days (~page/days-pills-from-data :days days)
:time-str time-str
:cost-str cost-str
:action-btn action-btn
:del-url del-url
:csrf-hdr csrf-hdr)))
(or slots (list)))))
:pre-action pre-action
:add-url add-url))
(defcomp ~page/ticket-type-col (&key label value)
(div :class "flex flex-col"
@@ -203,47 +226,87 @@
:onclick hide-js "Cancel"))))
;; Data-driven buy form — Python passes pre-resolved data, .sx does layout + iteration
(defcomp ~page/buy-form (&key entry-id info-sold info-remaining info-basket
ticket-types user-ticket-counts-by-type
user-ticket-count price-str adjust-url csrf state
my-tickets-href)
(if (!= state "confirmed")
(defcomp
~page/buy-form
(&key
entry-id
info-sold
info-remaining
info-basket
ticket-types
user-ticket-counts-by-type
user-ticket-count
price-str
adjust-url
csrf
state
my-tickets-href)
(if
(!= state "confirmed")
(~page/buy-not-confirmed :entry-id (str entry-id))
(let ((eid-s (str entry-id))
(target (str "#ticket-buy-" entry-id)))
(div :id (str "ticket-buy-" entry-id) :class "rounded-xl border border-stone-200 bg-white p-4"
(h3 :class "text-sm font-semibold text-stone-700 mb-3"
(i :class "fa fa-ticket mr-1" :aria-hidden "true") "Tickets")
;; Info bar
(when (or info-sold info-remaining info-basket)
(div :class "flex items-center gap-3 mb-3 text-xs text-stone-500"
(let
((eid-s (str entry-id)) (target (str "#ticket-buy-" entry-id)))
(div
:id (str "ticket-buy-" entry-id)
:class "rounded-xl border border-stone-200 bg-white p-4"
(h3
:class "text-sm font-semibold text-stone-700 mb-3"
(i :class "fa fa-ticket mr-1" :aria-hidden "true")
"Tickets")
(when
(or info-sold info-remaining info-basket)
(div
:class "flex items-center gap-3 mb-3 text-xs text-stone-500"
(when info-sold (span (str info-sold " sold")))
(when info-remaining (span (str info-remaining " remaining")))
(when info-basket
(span :class "text-emerald-600 font-medium"
(i :class "fa fa-shopping-cart text-[0.6rem]" :aria-hidden "true")
(when
info-basket
(span
:class "text-emerald-600 font-medium"
(i
:class "fa fa-shopping-cart text-[0.6rem]"
:aria-hidden "true")
(str " " info-basket " in basket")))))
;; Body — multi-type or default
(if (and ticket-types (not (empty? ticket-types)))
(div :class "space-y-2"
(map (fn (tt)
(let ((tt-count (if user-ticket-counts-by-type
(get user-ticket-counts-by-type (str (get tt "id")) 0)
0))
(tt-id (get tt "id")))
(div :class "flex items-center justify-between p-3 rounded-lg bg-stone-50 border border-stone-100"
(div (div :class "font-medium text-sm" (get tt "name"))
(div :class "text-xs text-stone-500" (get tt "cost_str")))
(~page/adjust-inline :csrf csrf :adjust-url adjust-url :target target
:entry-id eid-s :count tt-count :ticket-type-id tt-id
:my-tickets-href my-tickets-href))))
(if
(and ticket-types (not (empty? ticket-types)))
(div
:class "space-y-2"
(map
(fn
(tt)
(let-match
{:cost_str cost-str :id tt-id :name tt-name}
tt
(let
((tt-count (if user-ticket-counts-by-type (get user-ticket-counts-by-type (str tt-id) 0) 0)))
(div
:class "flex items-center justify-between p-3 rounded-lg bg-stone-50 border border-stone-100"
(div
(div :class "font-medium text-sm" tt-name)
(div :class "text-xs text-stone-500" cost-str))
(~page/adjust-inline
:csrf csrf
:adjust-url adjust-url
:target target
:entry-id eid-s
:count tt-count
:ticket-type-id tt-id
:my-tickets-href my-tickets-href)))))
ticket-types))
(<> (div :class "flex items-center justify-between mb-4"
(div (span :class "font-medium text-green-600" price-str)
(span :class "text-sm text-stone-500 ml-2" "per ticket")))
(~page/adjust-inline :csrf csrf :adjust-url adjust-url :target target
:entry-id eid-s :count (if user-ticket-count user-ticket-count 0)
:ticket-type-id nil :my-tickets-href my-tickets-href)))))))
(<>
(div
:class "flex items-center justify-between mb-4"
(div
(span :class "font-medium text-green-600" price-str)
(span :class "text-sm text-stone-500 ml-2" "per ticket")))
(~page/adjust-inline
:csrf csrf
:adjust-url adjust-url
:target target
:entry-id eid-s
:count (if user-ticket-count user-ticket-count 0)
:ticket-type-id nil
:my-tickets-href my-tickets-href)))))))
;; Inline +/- controls (used by both default and per-type)
(defcomp ~page/adjust-inline (&key csrf adjust-url target entry-id count ticket-type-id my-tickets-href)
@@ -285,26 +348,53 @@
"Tickets available once this event is confirmed."))
(defcomp ~page/buy-result (&key entry-id tickets remaining my-tickets-href)
(let ((count (len tickets))
(suffix (if (= count 1) "" "s")))
(div :id (str "ticket-buy-" entry-id) :class "rounded-xl border border-emerald-200 bg-emerald-50 p-4"
(div :class "flex items-center gap-2 mb-3"
(defcomp
~page/buy-result
(&key entry-id tickets remaining my-tickets-href)
(let
((count (len tickets)) (suffix (if (= count 1) "" "s")))
(div
:id (str "ticket-buy-" entry-id)
:class "rounded-xl border border-emerald-200 bg-emerald-50 p-4"
(div
:class "flex items-center gap-2 mb-3"
(i :class "fa fa-check-circle text-emerald-600" :aria-hidden "true")
(span :class "font-semibold text-emerald-800" (str count " ticket" suffix " reserved")))
(div :class "space-y-2 mb-4"
(map (fn (t)
(a :href (get t "href") :class "flex items-center justify-between p-2 rounded-lg bg-white border border-emerald-100 hover:border-emerald-300 transition text-sm"
(div :class "flex items-center gap-2"
(i :class "fa fa-ticket text-emerald-500" :aria-hidden "true")
(span :class "font-mono text-xs text-stone-500" (get t "code_short")))
(span :class "text-xs text-emerald-600 font-medium" "View ticket")))
(span
:class "font-semibold text-emerald-800"
(str count " ticket" suffix " reserved")))
(div
:class "space-y-2 mb-4"
(map
(fn
(t)
(let-match
{:href href :code_short code-short}
t
(a
:href href
:class "flex items-center justify-between p-2 rounded-lg bg-white border border-emerald-100 hover:border-emerald-300 transition text-sm"
(div
:class "flex items-center gap-2"
(i
:class "fa fa-ticket text-emerald-500"
:aria-hidden "true")
(span :class "font-mono text-xs text-stone-500" code-short))
(span
:class "text-xs text-emerald-600 font-medium"
"View ticket"))))
tickets))
(when (not (nil? remaining))
(let ((r-suffix (if (= remaining 1) "" "s")))
(p :class "text-xs text-stone-500" (str remaining " ticket" r-suffix " remaining"))))
(div :class "mt-3 flex gap-2"
(a :href my-tickets-href :class "text-sm text-emerald-700 hover:text-emerald-900 underline"
(when
(not (nil? remaining))
(let
((r-suffix (if (= remaining 1) "" "s")))
(p
:class "text-xs text-stone-500"
(str remaining " ticket" r-suffix " remaining"))))
(div
:class "mt-3 flex gap-2"
(a
:href my-tickets-href
:class "text-sm text-emerald-700 hover:text-emerald-900 underline"
"View all my tickets")))))
;; Single response wrappers for POST routes (include OOB cart icon)
@@ -477,27 +567,46 @@
(~page/post-img-placeholder)))
;; Entry posts nav OOB from data
(defcomp ~page/entry-posts-nav-oob-from-data (&key nav-btn posts)
(if (empty? (or posts (list)))
(defcomp
~page/entry-posts-nav-oob-from-data
(&key nav-btn posts)
(if
(empty? (or posts (list)))
(~page/entry-posts-nav-oob-empty)
(~page/entry-posts-nav-oob
:items (<> (map (lambda (p)
(~page/entry-nav-post
:href (get p "href") :nav-btn nav-btn
:img (~page/post-img-from-data :src (get p "img") :alt (get p "title"))
:title (get p "title")))
posts)))))
:items (<>
(map
(lambda
(p)
(let-match
{:href href :title title :img img}
p
(~page/entry-nav-post
:href href
:nav-btn nav-btn
:img (~page/post-img-from-data :src img :alt title)
:title title)))
posts)))))
;; Entry posts nav (non-OOB) from data — for desktop nav embedding
(defcomp ~page/entry-posts-nav-inner-from-data (&key posts)
(when (not (empty? (or posts (list))))
(defcomp
~page/entry-posts-nav-inner-from-data
(&key posts)
(when
(not (empty? (or posts (list))))
(~page/entry-posts-nav-oob
:items (<> (map (lambda (p)
(~page/entry-nav-post-link
:href (get p "href")
:img (~page/post-img-from-data :src (get p "img") :alt (get p "title"))
:title (get p "title")))
posts)))))
:items (<>
(map
(lambda
(p)
(let-match
{:href href :title title :img img}
p
(~page/entry-nav-post-link
:href href
:img (~page/post-img-from-data :src img :alt title)
:title title)))
posts)))))
;; Post nav entries+calendars OOB from data
(defcomp ~page/post-nav-wrapper-from-data (&key nav-btn entries calendars hyperscript)
@@ -602,14 +711,23 @@
(~shared:layout/nav-link :href admin-href :icon "fa fa-cog"))))
;; Post search results from data
(defcomp ~page/post-search-results-from-data (&key items page next-url has-more)
(defcomp
~page/post-search-results-from-data
(&key items page next-url has-more)
(<>
(map (lambda (item)
(~forms/post-search-item
:post-url (get item "post-url") :entry-id (get item "entry-id")
:csrf (get item "csrf") :post-id (get item "post-id")
:img (~page/post-img-from-data :src (get item "img") :alt (get item "title"))
:title (get item "title")))
(map
(lambda
(item)
(let-match
{:csrf csrf :entry-id entry-id :post-url post-url :title title :img img :post-id post-id}
item
(~forms/post-search-item
:post-url post-url
:entry-id entry-id
:csrf csrf
:post-id post-id
:img (~page/post-img-from-data :src img :alt title)
:title title)))
(or items (list)))
(cond
(has-more (~forms/post-search-sentinel :page page :next-url next-url))
@@ -617,16 +735,26 @@
(true ""))))
;; Entry options from data — state-driven button composition
(defcomp ~page/entry-options-from-data (&key entry-id state buttons)
(defcomp
~page/entry-options-from-data
(&key entry-id state buttons)
(~admin/entry-options
:entry-id entry-id
:buttons (<> (map (lambda (b)
(~admin/entry-option-button
:url (get b "url") :target (str "#calendar_entry_options_" entry-id)
:csrf (get b "csrf") :btn-type (get b "btn-type")
:action-btn (get b "action-btn")
:confirm-title (get b "confirm-title")
:confirm-text (get b "confirm-text")
:label (get b "label")
:is-btn (get b "is-btn")))
(or buttons (list))))))
:buttons (<>
(map
(lambda
(b)
(let-match
{:csrf csrf :confirm-title confirm-title :url url :btn-type btn-type :action-btn action-btn :confirm-text confirm-text :label label :is-btn is-btn}
b
(~admin/entry-option-button
:url url
:target (str "#calendar_entry_options_" entry-id)
:csrf csrf
:btn-type btn-type
:action-btn action-btn
:confirm-title confirm-title
:confirm-text confirm-text
:label label
:is-btn is-btn)))
(or buttons (list))))))