oficina / estilo Public

menu: a destructive row in the negative tone (Calls' correction)

estilo-menu-item="danger" and menu.Item.Danger. Calls, the fifth
adopter, is the first with a destructive item in a floating menu: End
meeting for everyone, in the meeting list's row menu. menu.css had no
tone for it, so a destructive row looked like any other and lit up in
the accent on hover. TestADangerItemIsDrawnInTheNegativeTone failed to
build before the field and passes after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev pushed by paul@keymail.dev 759a8ff71c5ab3061a028d8756ec8f9789145b21 parent cce1101
4 files changed, +39 −2
  • menu/menu.css +4 −0
  • menu/menu.go +6 −0
  • menu/menu_test.go +26 −0
  • menu/templates/menu.html +3 −2

CI passed — run details

CI log
=== modcache ===
all modules verified
=== vet ===
CGO_ENABLED=0 go vet ./...
=== fmt ===
=== build ===
CGO_ENABLED=0 go build -buildvcs=false ./...
=== go-test ===
CGO_ENABLED=0 go test -count=1 ./...
ok  	amadan.net/oficina/estilo.git/base	0.003s
ok  	amadan.net/oficina/estilo.git/brand	0.002s
ok  	amadan.net/oficina/estilo.git/busy	0.002s
ok  	amadan.net/oficina/estilo.git/callui	0.003s
ok  	amadan.net/oficina/estilo.git/chrome	0.180s
?   	amadan.net/oficina/estilo.git/cmd/oficina-notices	[no test files]
ok  	amadan.net/oficina/estilo.git/credits	0.005s
ok  	amadan.net/oficina/estilo.git/credits/catalog	0.004s
ok  	amadan.net/oficina/estilo.git/homeconnect	0.005s
ok  	amadan.net/oficina/estilo.git/internal/lint	0.004s
?   	amadan.net/oficina/estilo.git/internal/tokencheck	[no test files]
ok  	amadan.net/oficina/estilo.git/menu	0.008s
ok  	amadan.net/oficina/estilo.git/savestatus	0.080s
ok  	amadan.net/oficina/estilo.git/sky	0.021s
ok  	amadan.net/oficina/estilo.git/theme	0.006s
diff --git a/menu/menu.css b/menu/menu.css
index 2ddf029..9070652 100644
--- a/menu/menu.css
+++ b/menu/menu.css
@@ -63,6 +63,10 @@
[estilo-menu-item]:hover,
[estilo-menu-item]:focus-visible { background: var(--rst-accent-soft); }
[estilo-menu-item]:focus-visible { outline: 2px solid var(--rst-accent); outline-offset: -2px; }
+/* A destructive row: the negative tone, and its own tint on hover. The
+ label says what it does; the tone says it is the one to read twice. */
+[estilo-menu-item~="danger"] { color: var(--rst-tone-negative-fg); }
+[estilo-menu-item~="danger"]:hover, [estilo-menu-item~="danger"]:focus-visible { background: var(--rst-tone-negative-bg); }
[estilo-menu-item][aria-disabled="true"],
[estilo-menu-item]:disabled {
color: var(--rst-text-faint);
diff --git a/menu/menu.go b/menu/menu.go
index f143c47..9c21d78 100644
--- a/menu/menu.go
+++ b/menu/menu.go
@@ -51,6 +51,12 @@ type Item struct {
Action string
// Dialog is the id of the <dialog> a button row opens.
Dialog string
+ // Danger draws a destructive row in the negative tone
+ // (estilo-menu-item="danger"). Calls, the fifth adopter, was the
+ // first with one in a menu: "End meeting for everyone". The row still
+ // goes to its own confirm page; the tone only says it is the one to
+ // read twice.
+ Danger bool
}
// Apple reports whether the request comes from an Apple platform, the
diff --git a/menu/menu_test.go b/menu/menu_test.go
index 60c5158..40b50c2 100644
--- a/menu/menu_test.go
+++ b/menu/menu_test.go
@@ -161,3 +161,29 @@ func TestApplePlatformDetection(t *testing.T) {
}
}
}
+
+// A destructive row ("End meeting for everyone", Calls' first need) is
+// drawn in the negative tone, as a link, a POST or a dialog alike, and
+// its hover is the negative tint, not the accent: colour is not its only
+// cue — the label says what it does — but a red row that lights up blue
+// says two things at once.
+func TestADangerItemIsDrawnInTheNegativeTone(t *testing.T) {
+ for _, it := range []Item{{Href: "/m/x/end", Label: "End"}, {Action: "/m/x/end", Label: "End"}, {Dialog: "end", Label: "End"}} {
+ it.Danger = true
+ if html := render(t, "estilo-menu-item", it); !strings.Contains(html, `estilo-menu-item="danger"`) {
+ t.Fatalf("a danger row is not marked: %s", html)
+ }
+ it.Danger = false
+ if html := render(t, "estilo-menu-item", it); strings.Contains(html, "danger") {
+ t.Fatalf("a plain row is marked danger: %s", html)
+ }
+ }
+ for _, want := range []string{
+ `[estilo-menu-item~="danger"] { color: var(--rst-tone-negative-fg); }`,
+ `[estilo-menu-item~="danger"]:hover, [estilo-menu-item~="danger"]:focus-visible { background: var(--rst-tone-negative-bg); }`,
+ } {
+ if !strings.Contains(string(CSS()), want) {
+ t.Errorf("menu.css lacks %s", want)
+ }
+ }
+}
diff --git a/menu/templates/menu.html b/menu/templates/menu.html
index f0982b8..dd668ae 100644
--- a/menu/templates/menu.html
+++ b/menu/templates/menu.html
@@ -4,8 +4,9 @@
platform's invoker command). All three work with scripts off; chrome.js
only adds the dialog for engines without invoker commands. Callers
supply the label in their own locale; Key is an already-formatted
- shortcut (menu.Shortcut). */}}
-{{define "estilo-menu-item"}}{{if .Action}}<form method="post" action="{{.Action}}"><button estilo-menu-item type="submit">{{template "estilo-menu-item-body" .}}</button></form>{{else if .Dialog}}<button estilo-menu-item type="button" commandfor="{{.Dialog}}" command="show-modal">{{template "estilo-menu-item-body" .}}</button>{{else}}<a estilo-menu-item href="{{.Href}}">{{template "estilo-menu-item-body" .}}</a>{{end}}{{end}}
+ shortcut (menu.Shortcut). Danger marks a destructive row
+ (estilo-menu-item="danger"), drawn in the negative tone. */}}
+{{define "estilo-menu-item"}}{{if .Action}}<form method="post" action="{{.Action}}"><button estilo-menu-item{{if .Danger}}="danger"{{end}} type="submit">{{template "estilo-menu-item-body" .}}</button></form>{{else if .Dialog}}<button estilo-menu-item{{if .Danger}}="danger"{{end}} type="button" commandfor="{{.Dialog}}" command="show-modal">{{template "estilo-menu-item-body" .}}</button>{{else}}<a estilo-menu-item{{if .Danger}}="danger"{{end}} href="{{.Href}}">{{template "estilo-menu-item-body" .}}</a>{{end}}{{end}}
{{define "estilo-menu-item-body"}}<span estilo-menu-label>{{.Label}}</span>{{with .Key}}<kbd estilo-menu-key>{{.}}</kbd>{{end}}{{end}}
{{/* estilo-icon-check: the glyph inside an estilo-menu-check column.
Lucide "check" (ISC, brand/LICENSE.lucide). */}}