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
(-> (routes
docs/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 row
src/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")))))