WIP: the busy rule from estilo (spinner ≥650ms, reset on Back)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5 files changed,
+41
−5
go.mod+1 −1go.sum+2 −0internal/docs/render.go+12 −4internal/docs/theme_assets_test.go+24 −0internal/docs/views/layout.html+2 −0
Intent
diff --git a/go.mod b/go.mod| index 3de4f0f..350c675 100644 |
| --- a/go.mod |
| +++ b/go.mod |
| @@ -3,7 +3,7 @@ module github.com/carlosframework/docs |
| go 1.26.5 |
| require ( |
| - amadan.net/oficina/estilo.git v0.0.0-20260929070530-5b2ce835e773 |
| + amadan.net/oficina/estilo.git v0.0.0-20260929095046-d0664ee49914 |
| amadan.net/oficina/home/handoff v0.0.0-20260908102137-481d2d57f489 |
| amadan.net/oficina/home/roster v0.0.0-20260908123429-74dda570e0cf |
| amadan.net/rastrillo/native/server v0.0.0-20260923173546-2a5d07b2c8d3 |
diff --git a/go.sum b/go.sum| index 62acbd3..a137f12 100644 |
| --- a/go.sum |
| +++ b/go.sum |
| @@ -2,6 +2,8 @@ amadan.net/oficina/estilo.git v0.0.0-20260924071126-894afdcb71a9 h1:vHOkWeJ7mRwL |
| amadan.net/oficina/estilo.git v0.0.0-20260924071126-894afdcb71a9/go.mod h1:4u0jNBtIdMLOsJyrScfqoEuYDJF7BBM3qM9YWbzt6aw= |
| amadan.net/oficina/estilo.git v0.0.0-20260929070530-5b2ce835e773 h1:9DIAhG3TaGG+LE2+8LvU73ZEhfK+oNn2y2p4kFjAAi0= |
| amadan.net/oficina/estilo.git v0.0.0-20260929070530-5b2ce835e773/go.mod h1:4u0jNBtIdMLOsJyrScfqoEuYDJF7BBM3qM9YWbzt6aw= |
| +amadan.net/oficina/estilo.git v0.0.0-20260929095046-d0664ee49914 h1:V0AyxXYCxqGZClOLqvppw+cCTXsn3b9NmRQ8CV5ZlXY= |
| +amadan.net/oficina/estilo.git v0.0.0-20260929095046-d0664ee49914/go.mod h1:4u0jNBtIdMLOsJyrScfqoEuYDJF7BBM3qM9YWbzt6aw= |
| amadan.net/oficina/home/handoff v0.0.0-20260908102137-481d2d57f489 h1:RPKtyry/71i06o8L+gjAaUyexbmG56kO1YPs601URAM= |
| amadan.net/oficina/home/handoff v0.0.0-20260908102137-481d2d57f489/go.mod h1:YISpzFJ+iMgrM0rUL032OSzsAQbP4/nozvXLCCOcn1k= |
| amadan.net/oficina/home/roster v0.0.0-20260908123429-74dda570e0cf h1:SRKjCUgtk42WwR7VSn+V0eLNk20mfiY8dQmzdw13Sbo= |
diff --git a/internal/docs/render.go b/internal/docs/render.go| index 536f1cc..70f89c9 100644 |
| --- a/internal/docs/render.go |
| +++ b/internal/docs/render.go |
| @@ -10,6 +10,7 @@ import ( |
| "testing/fstest" |
| "amadan.net/oficina/estilo.git/base" |
| + "amadan.net/oficina/estilo.git/busy" |
| "amadan.net/oficina/estilo.git/chrome" |
| estilomenu "amadan.net/oficina/estilo.git/menu" |
| "amadan.net/oficina/estilo.git/savestatus" |
| @@ -625,7 +626,8 @@ func (rd *renderer) pageStatus(w http.ResponseWriter, r *http.Request, name stri |
| // vendoredStaticFS builds the static/ tree rastrillo.Assets serves — |
| // estilo's theme for Docs (Calma, with Docs' accent; estilo AGENTS.md, |
| // decisions table, row 8), plus tokens.css and the three scripts |
| -// the shell's <head> links unconditionally (ui.ShimJS/SelectJS/DatetimeJS) |
| +// the shell's <head> links unconditionally (the shim and busy rule from |
| +// estilo's busy; ui.SelectJS/DatetimeJS) |
| // taken straight from the selected rastrillo module. Nothing here is copied to |
| // static/ by hand: rastrillo new would normally vendor these to disk |
| // once at scaffold time (ui.VendoredAssets's own doc comment), but |
| @@ -643,9 +645,15 @@ func vendoredStaticFS() (fs.FS, error) { |
| "static/estilo-save-status.mjs": {Data: savestatus.JS()}, |
| "static/tokens.css": {Data: ui.TokensCSS()}, |
| "static/theme.css": {Data: docsTheme}, |
| - "static/rastrillo.js": {Data: ui.ShimJS()}, |
| - "static/select.js": {Data: ui.SelectJS()}, |
| - "static/datetime.js": {Data: ui.DatetimeJS()}, |
| + // rastrillo's busy rule (Paul, 2026-09-29): a submit button's |
| + // label gives way to a spinner for at least 650ms, the button |
| + // disables, and Back resets it. estilo carries rastrillo's shim |
| + // without the old busy rule, busy.js and the spinner-in-place CSS. |
| + "static/rastrillo.js": {Data: busy.ShimJS()}, |
| + "static/estilo/busy.js": {Data: busy.JS()}, |
| + "static/estilo/busy.css": {Data: busy.CSS()}, |
| + "static/select.js": {Data: ui.SelectJS()}, |
| + "static/datetime.js": {Data: ui.DatetimeJS()}, |
| // estilo's shared chrome, fingerprinted like the theme. |
| "static/estilo/base.css": {Data: base.CSS()}, |
| "static/estilo/menu.css": {Data: estilomenu.CSS()}, |
diff --git a/internal/docs/theme_assets_test.go b/internal/docs/theme_assets_test.go| index 0d04693..55d3e16 100644 |
| --- a/internal/docs/theme_assets_test.go |
| +++ b/internal/docs/theme_assets_test.go |
| @@ -1,6 +1,9 @@ |
| package docs |
| import ( |
| + "path" |
| + |
| + "amadan.net/oficina/estilo.git/busy" |
| "bytes" |
| "crypto/sha256" |
| "fmt" |
| @@ -76,3 +79,24 @@ func TestOnestIsLinkedAndServed(t *testing.T) { |
| } |
| } |
| } |
| + |
| +// The busy rule (estilo's busy): the page links busy.css and busy.js, and |
| +// the shim it serves is estilo's — rastrillo's without the old busy rule, |
| +// which would fight the new one. |
| +func TestBusyRuleIsLinkedAndServed(t *testing.T) { |
| + app, err := NewForTest(t) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + page := serve(app, httptest.NewRequest(http.MethodGet, "/signin", nil)).Body.String() |
| + for name, body := range map[string][]byte{"static/estilo/busy.css": busy.CSS(), "static/rastrillo.js": busy.ShimJS(), "static/estilo/busy.js": busy.JS()} { |
| + sum := sha256.Sum256(body) |
| + hashed := "/" + strings.TrimSuffix(name, path.Ext(name)) + fmt.Sprintf(".%x", sum[:8]) + path.Ext(name) |
| + if !strings.Contains(page, `"`+hashed+`"`) { |
| + t.Errorf("sign-in page does not link %s", hashed) |
| + } |
| + if res := serve(app, httptest.NewRequest(http.MethodGet, hashed, nil)); res.Code != http.StatusOK || !bytes.Equal(res.Body.Bytes(), body) { |
| + t.Errorf("GET %s = %d or the wrong bytes", hashed, res.Code) |
| + } |
| + } |
| +} |
diff --git a/internal/docs/views/layout.html b/internal/docs/views/layout.html| index 09f7e8e..afcdb10 100644 |
| --- a/internal/docs/views/layout.html |
| +++ b/internal/docs/views/layout.html |
| @@ -19,12 +19,14 @@ |
| <title>{{range .Frame.TitleParts}}{{.}} · {{end}}{{.Frame.Chrome.App.Name}}</title> |
| <link rel="stylesheet" href="/static/estilo/fonts/v1/fonts.css"> |
| <link rel="stylesheet" href="{{asset "static/tokens.css"}}"> |
| +<link rel="stylesheet" href="{{asset "static/estilo/busy.css"}}"> |
| <link rel="stylesheet" href="{{asset "static/theme.css"}}"> |
| {{template "head" .}} |
| <link rel="stylesheet" href="{{asset "static/estilo/base.css"}}"> |
| <link rel="stylesheet" href="{{asset "static/estilo/menu.css"}}"> |
| <link rel="stylesheet" href="{{asset "static/estilo/chrome.css"}}"> |
| <script defer src="{{asset "static/rastrillo.js"}}"></script> |
| +<script defer src="{{asset "static/estilo/busy.js"}}"></script> |
| {{iconAssets}} |
| <script defer src="{{asset "static/select.js"}}"></script> |
| <script defer src="{{asset "static/datetime.js"}}"></script> |