Commita3d62c55Recorded6 Jul 2026Repositorysigil-web
Resolve [DW] doc review comments; rename flash-message -> sg-flash-message
Message
Address the review round on the sigil-web docs: - ui.md: note that http-response/sxml does not auto-inject the client script and show how to inject it (route form + inline sigil-web-ui-script); document the ui-flash target: override; rename the "Components" heading to "UI Components"; clarify sg-link fetch/apply semantics; drop the "Deprecated Aliases" section. - live.md: remove the heartbeat-explanation prose, keep the with-async boilerplate visible in the example. - routing.md: show http-response/json taking a dict directly.
Rename the flash SXML widget flash-message -> sg-flash-message so it matches the sg-* widget naming convention (binding, export, ui-flash internal call, docstrings, and tests). No compatibility alias.
Changed
docs/live.md | 11 +++++------
docs/routing.md | 10 +++++-----
docs/ui.md | 51 +++++++++++++++++++++++++++++++++------------------
src/sigil/web/ui.sgl | 26 +++++++++++++-------------
test/test-ui.sgl | 18 +++++++++---------
5 files changed, 65 insertions(+), 51 deletions(-)Diff
docs/live.mdmodified
@@ -21,8 +21,7 @@ A mutation/push is what **everyone** sees; a handler's return value is what the
21
**acting user** sees (usually an empty `ui-response`, or nothing under 22
`ui-routes`). 23
−24
The client is served by `with-sigil-ui` and injected by `http-response/page`;−25
each stream's endpoint is registered with `live-routes`.+24
The client JavaScript is served by `with-sigil-ui` middleware and injected by `http-response/page`; each stream's endpoint is registered with `live-routes`. 25
26
## Collections 27
@@ -138,12 +137,12 @@ Combine it with your app's routes.
137
(router …item routes…) 138
(live-routes todos) ; registers /events 139
(live-routes log)) ; registers /log−141
(with-sigil-ui) (with-logging) (with-not-found)))+140
(with-sigil-ui)+141
(with-logging)+142
(with-not-found))) 143
``` 144
−144
The heartbeat starts lazily on the first connection (inside the server's async−145
context), so `main` is just `(with-async (http-serve app port: 8080))` — no−146
explicit start call.+145
Serve it as usual: `main` is just `(with-async (http-serve app port: 8080))`. 146
147
## Common Patterns 148
docs/routing.mdmodified
@@ -103,7 +103,9 @@ is taken as "handled, nothing extra for the acting user" and becomes an empty
103
(-> (routes 104
(ui-routes (router …hypermedia…)) ; non-response returns -> empty ack 105
(router …json-api…)) ; plain routes never coerce−106
(with-sigil-ui) (with-logging) (with-not-found)))+106
(with-sigil-ui)+107
(with-logging)+108
(with-not-found))) 109
``` 110
111
`#f` still falls through (a missing-id mutation → 404 via `with-not-found`), and@@ -233,16 +235,14 @@ Path traversal attacks (`..`) are blocked by `safe-path?`.
235
236
```scheme 237
(import (sigil web)−236
(sigil http)−237
(sigil json))+238
(sigil http)) 239
240
(define (home-handler request) 241
(http-response/html 200 "<h1>Home</h1>")) 242
243
(define (user-handler request) 244
(let ((id (path-param request "id")))−244
(http-response/json 200−245
(json-encode #{ id: id }))))+245
(http-response/json 200 #{ id: id }))) ; a dict is encoded for you 246
247
(define app 248
(-> (routesdocs/ui.mdmodified
@@ -39,6 +39,27 @@ HTML document. Prepends `<!DOCTYPE html>`, sets `text/html`.
39
`(html (head (title "Hi")) (body (h1 "Hi")))) 40
``` 41
+42
Because you bring your own `<html>`, the client script is **not** auto-injected+43
here (only `http-response/page` injects it). Add it yourself, either by pointing+44
a `<script>` at the route `with-sigil-ui` serves, or by inlining the bundle with+45
`sigil-web-ui-script`:+46
+47
```scheme+48
(http-response/sxml+49
`(html+50
(head (title "Hi")+51
;; served by with-sigil-ui:+52
(script (@ (src "/js/sigil-web-ui.js"))))+53
(body (h1 "Hi"))))+54
+55
;; ...or inline the bundle, no route needed:+56
(http-response/sxml+57
`(html+58
(head (title "Hi")+59
(script ,(sigil-web-ui-script)))+60
(body (h1 "Hi"))))+61
```+62
63
### with-sigil-ui 64
65
Middleware that serves the client script at `/js/sigil-web-ui.js`, so you never@@ -116,12 +137,14 @@ Run a limited client command (things HTML state can't express).
137
### ui-flash 138
139
Push a flash message (append into a container, default `#sg-flash-container`).+140
Pass `target:` to append into a different container. 141
142
```scheme 143
(ui-flash type: 'success message: "Saved!" remove-after: 3000)+144
(ui-flash type: 'error message: "Nope" target: "#form-errors") 145
``` 146
−124
Also available: `ui-css-reload`, `ui-js`, and `flash-message` (the flash SXML+147
Also available: `ui-css-reload`, `ui-js`, and `sg-flash-message` (the flash SXML 148
without pushing it). 149
150
## Responding to an Action@@ -145,7 +168,7 @@ not drive the UI; surface an error by targeting an error element.
168
`ui-routes` (see the routing docs) lets a handler skip the empty `ui-response` 169
and just end with its mutation. 170
−148
## Components+171
## UI Components 172
173
`sg-*` helpers emit HTML with `data-sg-*` attributes; the client fetches the 174
route and applies the result — you never write the fetch. Action helpers accept@@ -160,8 +183,13 @@ an HTTP-method **symbol** and default to `POST`.
183
184
### sg-link 185
−163
A link that morphs a fetched fragment into `target:` (falls back to normal−164
navigation for non-JS clients). Children are a string or a list of nodes.+186
A link that fetches the `action:` route on click and applies the response,+187
instead of navigating. When `target:` is set the client cancels the normal+188
navigation, GETs `action:`, and applies what comes back: a plain HTML fragment is+189
morphed into `target:`, while a `ui-response` batch is applied as its `ui-*`+190
updates (those name their own targets, so `target:` is just the fragment+191
fallback). With no `target:` it stays an ordinary link, so non-JS clients still+192
navigate. Children are a string or a list of nodes. 193
194
```scheme 195
(sg-link "Edit" action: "/items/1/edit" target: "#item-1")@@ -191,26 +219,13 @@ the attribute); `label:`/`error:` wrap the field in a `<div>` with a `<label>`.
219
220
## Widgets 221
−194
- `flash-message type: message: (keys: remove-after:)` — a `role="alert"` box.+222
- `sg-flash-message type: message: (keys: remove-after:)` — a `role="alert"` box. 223
- `sg-loading-indicator` — a spinner element toggled during actions. 224
- `sg-modal` / `sg-modal-trigger` / `sg-modal-close` — `<dialog>`-based modals. 225
- `sg-data-table columns: rows: (keys: row-actions:)` + `sg-table-action`. 226
- `sg-paginator current-page: total-pages: base-url: (keys: target:)`. 227
- `sg-sse children (keys: url:)` — an SSE-subscribed container. 228
−201
## Deprecated Aliases−202
−203
The UI-update family shipped as `sse-*`; the current names are `ui-*`. These−204
aliases still work (same bindings) through 1.0 — prefer the `ui-*` names in new−205
code:−206
−207
- `sse-morph` → `ui-update`; `sse-remove`/`sse-class`/`sse-eval`/`sse-redirect`/−208
`sse-reload`/`sse-css-reload`/`sse-js`/`sse-flash` → the matching `ui-*`.−209
- `sse-response-batch` → `ui-response`.−210
- `sigil-ui-response target: content: (keys: (mode) (status))` is the older−211
single-fragment actor response (HTML body + `Sigil-UI-Merge-*` headers). It−212
still works; prefer `ui-response` with a `ui-update` for one consistent model.−213
229
## Common Patterns 230
231
### A form that resets itself and broadcasts a new rowsrc/sigil/web/ui.sglmodified
@@ -90,7 +90,7 @@
90
sg-submit-button 91
92
;; Flash messages−93
flash-message+93
sg-flash-message 94
95
;; Loading indicator 96
sg-loading-indicator@@ -968,14 +968,14 @@
968
;;; 969
;;; Example: 970
;;; ```scheme−971
;;; (flash-message type: 'success message: "Saved!" remove-after: 3000)−972
;;; (flash-message type: 'error message: "Failed to save")+971
;;; (sg-flash-message type: 'success message: "Saved!" remove-after: 3000)+972
;;; (sg-flash-message type: 'error message: "Failed to save") 973
;;; ```−974
(define (flash-message (keys: (type 'info)−975
(message "")−976
(remove-after #f)−977
(class #f)−978
(id #f)))+974
(define (sg-flash-message (keys: (type 'info)+975
(message "")+976
(remove-after #f)+977
(class #f)+978
(id #f))) 979
(let ((type-class (string-append "sg-flash sg-flash-" 980
(symbol->string type)))) 981
`(div (@ (role "alert")@@ -1004,11 +1004,11 @@
1004
(id #f))) 1005
(ui-update target: target 1006
mode: "append"−1007
content: (flash-message type: type−1008
message: message−1009
remove-after: remove-after−1010
class: class−1011
id: id)))+1007
content: (sg-flash-message type: type+1008
message: message+1009
remove-after: remove-after+1010
class: class+1011
id: id))) 1012
1013
;; Deprecated alias (see the sse-* alias block above). 1014
(define sse-flash ui-flash)test/test-ui.sglmodified
@@ -398,41 +398,41 @@
398
(assert-true (string-contains? html "/my-sigil.js")))))) 399
400
;; ============================================================−401
;; flash-message+401
;; sg-flash-message 402
;; ============================================================ 403
−404
(test-group "flash-message"+404
(test-group "sg-flash-message" 405
(test "basic flash with type"−406
(let ((html (sxml->xml (flash-message type: 'success message: "Saved!"))))+406
(let ((html (sxml->xml (sg-flash-message type: 'success message: "Saved!")))) 407
(assert-true (string-contains? html "role=\"alert\"")) 408
(assert-true (string-contains? html "sg-flash sg-flash-success")) 409
(assert-true (string-contains? html "Saved!")))) 410
411
(test "default type is info"−412
(let ((html (sxml->xml (flash-message message: "Hello"))))+412
(let ((html (sxml->xml (sg-flash-message message: "Hello")))) 413
(assert-true (string-contains? html "sg-flash-info")))) 414
415
(test "with remove-after"−416
(let ((html (sxml->xml (flash-message type: 'warning message: "Watch out"+416
(let ((html (sxml->xml (sg-flash-message type: 'warning message: "Watch out" 417
remove-after: 3000)))) 418
(assert-true (string-contains? html "data-sg-remove-after=\"3000\"")))) 419
420
(test "without remove-after"−421
(let ((html (sxml->xml (flash-message type: 'error message: "Oops"))))+421
(let ((html (sxml->xml (sg-flash-message type: 'error message: "Oops")))) 422
(assert-false (string-contains? html "data-sg-remove-after")))) 423
424
(test "with custom class"−425
(let ((html (sxml->xml (flash-message type: 'info message: "Note"+425
(let ((html (sxml->xml (sg-flash-message type: 'info message: "Note" 426
class: "my-flash")))) 427
(assert-true (string-contains? html "sg-flash sg-flash-info my-flash")))) 428
429
(test "with id"−430
(let ((html (sxml->xml (flash-message type: 'success message: "OK"+430
(let ((html (sxml->xml (sg-flash-message type: 'success message: "OK" 431
id: "flash-1")))) 432
(assert-true (string-contains? html "id=\"flash-1\"")))) 433
434
(test "SXML list message"−435
(let ((html (sxml->xml (flash-message type: 'info+435
(let ((html (sxml->xml (sg-flash-message type: 'info 436
message: '((strong "Bold") " text"))))) 437
(assert-true (string-contains? html "<strong>Bold</strong>")) 438
(assert-true (string-contains? html " text")))))