chrome: the account can sit at a sidebar rail's foot and open upward
Account.Rail, for an app whose chrome is a rail rather than the bar (Fichas; Paul, 2026-10-01: "put any shared stuff in the bottom left and maybe roll it into a dropup"). The menu, its group and its one Sign out are unchanged. In a rail: - the trigger is a square, rail-wide row: the avatar, then who is signed in (estilo-account-who), ellipsised; - the panel hangs from the rail's inline start, since from the inline end of a foot narrower than the panel it would leave the window; - from 800px, where the rail is the viewport's height, it opens upward. Below that the rail is a disclosure at the top of the page and the panel opens downward, as rastrillo's own rail-foot dropdown does. TestTheAccountCanSitAtARailsFoot failed before the change on the marking, the trigger row and both placement rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 files changed,
+85
−2
chrome/chrome.css+28 −0chrome/chrome.go+9 −0chrome/chrome_test.go+41 −0chrome/templates/account.html+7 −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.006s ok amadan.net/oficina/estilo.git/brand 0.005s ok amadan.net/oficina/estilo.git/busy 0.003s ok amadan.net/oficina/estilo.git/callui 0.006s ok amadan.net/oficina/estilo.git/chrome 0.221s ? amadan.net/oficina/estilo.git/cmd/oficina-notices [no test files] ok amadan.net/oficina/estilo.git/credits 0.006s 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.010s ok amadan.net/oficina/estilo.git/savestatus 0.082s ok amadan.net/oficina/estilo.git/sky 0.024s ok amadan.net/oficina/estilo.git/theme 0.006s
diff --git a/chrome/chrome.css b/chrome/chrome.css| index 74bd3d7..38e5c7e 100644 |
| --- a/chrome/chrome.css |
| +++ b/chrome/chrome.css |
| @@ -208,6 +208,34 @@ |
| [estilo-menu-identity-name] + [estilo-menu-identity-address] { font-size: var(--rst-fs-xs); } |
| [estilo-bar-signin] { flex: none; } |
| +/* The account at a sidebar rail's foot (Account.Rail), for an app whose |
| + chrome is a rail rather than the bar. The trigger is a row the rail's |
| + width: the avatar, then who is signed in, cut short with an ellipsis. |
| + It stays in the page flow, so its highlight stays square. The panel |
| + hangs from the rail's inline start: from the inline end of a foot |
| + narrower than the panel it would run off the window's start edge. */ |
| +[estilo-account~="rail"] > [estilo-bar-trigger] { |
| + justify-content: flex-start; |
| + gap: var(--rst-sp-2); |
| + inline-size: 100%; |
| + block-size: auto; |
| + min-block-size: 2.5rem; |
| + color: var(--rst-text); |
| +} |
| +[estilo-account-who] { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
| +[estilo-account~="rail"] > [estilo-account-panel] { inset-inline-start: 0; inset-inline-end: auto; } |
| +/* A drop-up where the rail is the viewport's height (rastrillo's shell |
| + breakpoint): below the foot there is only the window's edge. Below |
| + 800px the rail is a disclosure at the top of the page, with the page |
| + under it and too little above, so the panel opens downward there. */ |
| +@media (min-width: 800px) { |
| + [estilo-account~="rail"] > [estilo-account-panel] { |
| + inset-block-start: auto; |
| + inset-block-end: calc(100% + 4px); |
| + max-block-size: max(calc(100dvh - 5rem), 8rem); |
| + } |
| +} |
| + |
| /* Visually hidden, still announced. */ |
| [estilo-visually-hidden] { position: absolute; clip-path: inset(50%); inline-size: 1px; block-size: 1px; overflow: hidden; white-space: nowrap; } |
diff --git a/chrome/chrome.go b/chrome/chrome.go| index 4253bdc..a51b5b4 100644 |
| --- a/chrome/chrome.go |
| +++ b/chrome/chrome.go |
| @@ -141,6 +141,15 @@ type Account struct { |
| // Help items come last before Sign out ("Open source" last of all). |
| Help []menu.Item |
| SignOut SignOut |
| + // Rail draws the account at the foot of a sidebar rail rather than at |
| + // the bar's inline end, for an app whose chrome is a rail (Fichas). |
| + // The trigger becomes a row that says who is signed in beside the |
| + // avatar, and the panel hangs from the rail's inline start and opens |
| + // upward where the rail is the viewport's height (from 800px, the |
| + // shell breakpoint). Below that the rail is an in-flow disclosure at |
| + // the top of the page, so the panel opens downward like every other. |
| + // The menu, its group and its one Sign out are unchanged. |
| + Rail bool |
| } |
| // SignOut is the account menu's one Sign out: a POST to Action. |
diff --git a/chrome/chrome_test.go b/chrome/chrome_test.go| index 40cc07d..a1fff3c 100644 |
| --- a/chrome/chrome_test.go |
| +++ b/chrome/chrome_test.go |
| @@ -513,3 +513,44 @@ func TestTitleFieldDoesNotZoomOnTap(t *testing.T) { |
| t.Error("the coarse-pointer size comes before the title field's own rule, whose font: inherit then wins") |
| } |
| } |
| + |
| +// The account at a sidebar's foot (Account.Rail), for an app whose chrome |
| +// is a rail, not the bar (Fichas, Paul 2026-10-01: "put any shared stuff in |
| +// the bottom left and maybe roll it into a dropup"). The same menu, the |
| +// same group and the same one Sign out; only where it sits changes. |
| +func TestTheAccountCanSitAtARailsFoot(t *testing.T) { |
| + acc := signedIn("x").Account |
| + bar := render(t, "estilo-account", acc) |
| + if strings.Contains(bar, `estilo-account="rail"`) || strings.Contains(bar, "estilo-account-who") { |
| + t.Error("the bar's account changed: Rail is off by default") |
| + } |
| + acc.Rail = true |
| + html := render(t, "estilo-account", acc) |
| + if !strings.Contains(html, `<details estilo-account="rail" name="estilo-menus">`) { |
| + t.Errorf("a rail account is not marked, or left the one menu group:\n%s", html) |
| + } |
| + // In a rail the trigger is a row: who is signed in, beside the avatar. |
| + summary := html[strings.Index(html, "<summary"):strings.Index(html, "</summary>")] |
| + if !strings.Contains(summary, `<span estilo-account-who dir="auto">paul@example.test</span>`) { |
| + t.Errorf("the rail trigger does not say who is signed in:\n%s", summary) |
| + } |
| + if n := strings.Count(html, "x-signout"); n != 1 { |
| + t.Errorf("Sign out rendered %d times", n) |
| + } |
| + |
| + src := string(CSS()) |
| + // It opens upward where the rail is the viewport's height, and from the |
| + // rail's inline start: hung from the inline end of a foot narrower than |
| + // the panel, it would run off the window's start edge. |
| + wide := regexp.MustCompile(`@media \(min-width: 800px\) \{\s*\[estilo-account~="rail"\] > \[estilo-account-panel\] \{[^}]*inset-block-start: auto;[^}]*inset-block-end: calc\(100% \+ 4px\);`) |
| + if !wide.MatchString(src) { |
| + t.Error("chrome.css does not open the rail account upward from 800px") |
| + } |
| + if !regexp.MustCompile(`\[estilo-account~="rail"\] > \[estilo-account-panel\] \{[^}]*inset-inline-start: 0;[^}]*inset-inline-end: auto;`).MatchString(src) { |
| + t.Error("chrome.css does not hang the rail account's panel from the rail's inline start") |
| + } |
| + // The trigger is in the page flow: square, like every bar trigger. |
| + if regexp.MustCompile(`\[estilo-account~="rail"\] > \[estilo-bar-trigger\] \{[^}]*border-radius: (?:[1-9]|var)`).MatchString(src) { |
| + t.Error("the rail trigger is rounded; an in-flow highlight is square") |
| + } |
| +} |
diff --git a/chrome/templates/account.html b/chrome/templates/account.html| index ce0f461..0b5b20d 100644 |
| --- a/chrome/templates/account.html |
| +++ b/chrome/templates/account.html |
| @@ -5,10 +5,15 @@ |
| items, Appearance, Help, then Sign out, which appears exactly once in |
| the app. Appearance is a form of three buttons; the current one is |
| aria-pressed="true" and every other "false", and the check glyph, not |
| - colour, shows which. */}} |
| -{{define "estilo-account"}}<details estilo-account name="estilo-menus"> |
| + colour, shows which. |
| + |
| + Rail (Account.Rail) puts it at a sidebar rail's foot instead: the |
| + trigger is a row naming who is signed in, and the panel opens upward |
| + where the rail is the viewport's height. */}} |
| +{{define "estilo-account"}}<details estilo-account{{if .Rail}}="rail"{{end}} name="estilo-menus"> |
| <summary estilo-bar-trigger aria-label="{{.TriggerLabel}}"> |
| {{- if .Initials}}<span estilo-avatar aria-hidden="true">{{.Initials}}</span>{{else}}{{template "estilo-icon-person"}}{{end -}} |
| + {{- if .Rail}}{{with or .Name .Address}}<span estilo-account-who dir="auto">{{.}}</span>{{end}}{{end -}} |
| </summary> |
| <div estilo-menu estilo-account-panel> |
| <div estilo-menu-identity> |