oficina / calendar Public

Keep Calendar chrome consistent on errors

Paul Campbell pushed by paul@keymail.dev 3d8ea7b374eb554aca8a4fc59aeaab6e53b09361 parent 4e008a0
5 files changed, +50 −45
  • internal/calendar/chrome_calendar.go +42 −0
  • internal/calendar/home_router_test.go +5 −1
  • internal/calendar/render.go +0 −41
  • internal/calendar/static/calendar.css +1 −1
  • internal/calendar/templates/layout.html +2 −2

CI failed — run details

CI log
=== vet ===
go vet ./...
go: downloading amadan.net/oficina/home/connect v0.0.0-20260923190447-01166fcfc0a9
go: downloading amadan.net/oficina/estilo.git v0.0.0-20260924071126-894afdcb71a9
go: downloading amadan.net/rastrillo/native/server v0.0.0-20260923173546-2a5d07b2c8d3
=== fmt ===
=== test ===
go test ./...
?   	amadan.net/oficina/calendar/cmd/calendar	[no test files]
ok  	amadan.net/oficina/calendar/internal/calendar	4.503s
?   	amadan.net/oficina/calendar/internal/calendar/icons	[no test files]
?   	amadan.net/oficina/calendar/internal/calendar/seal	[no test files]
--- FAIL: TestABookedEventsFormLocksItsTimeButSavesItsTitle (0.02s)
    booking_settings_test.go:150: reschedule: changed=false err=calendar: slot taken
2026/09/24 14:36:18 INFO migrate applied="[sessions/0001_init auth/0001_init auth/0002_sessions_backfill calendar/0001_create_people calendar/0002_alter_people calendar/0003_alter_reminders calendar/0004_create_attendees calendar/0005_alter_people calendar/0006_alter_reminder_queues]" skipped=0 adopted=false
2026/09/24 14:36:18 ERROR calendar: scheduled work registration failed stage=schedule.register err="calendar: this platform does not support safe scheduled work; update the platform before starting Calendar"
FAIL
FAIL	amadan.net/oficina/calendar/internal/calendartest	2.112s
ok  	amadan.net/oficina/calendar/internal/guardstest	0.041s
FAIL
make: *** [Makefile:85: test] Error 1

amadan: step test failed: exit status 2

amadan: job failed at step test — its output is above, not at the tail of this log
diff --git a/internal/calendar/chrome_calendar.go b/internal/calendar/chrome_calendar.go
new file mode 100644
index 0000000..ee5571e
--- /dev/null
+++ b/internal/calendar/chrome_calendar.go
@@ -0,0 +1,42 @@
+package calendar
+
+import (
+ "html/template"
+ "net/http"
+
+ "amadan.net/oficina/estilo.git/chrome"
+ estilomenu "amadan.net/oficina/estilo.git/menu"
+ rauth "github.com/carlosframework/rastrillo/auth"
+)
+
+func (h *handlers) viewer(r *http.Request) (rauth.Identity, bool) {
+ if h.viewerFrom != nil {
+ return h.viewerFrom(r)
+ }
+ return viewer(r)
+}
+
+func calendarChrome(r *http.Request, signedIn bool, address string, mode chrome.Mode, title, directory string) chrome.Bar {
+ current := func(prefix string) bool {
+ return r.URL.Path == prefix || (prefix != "/" && len(r.URL.Path) > len(prefix) && r.URL.Path[:len(prefix)] == prefix)
+ }
+ bar := chrome.Bar{
+ App: chrome.App{Name: "Calendar", Slug: "calendar", Home: "/", Icon: assets.Path("static/brand/icon.svg")}, Sticky: true,
+ Labels: chrome.BarLabels{Skip: "Skip to content", Menu: "Menu"}, NavLabel: "Calendar",
+ Nav: []chrome.Link{{Href: "/today", Label: "Today", Current: current("/today") || current("/day/") || current("/week/") || current("/month/")}, {Href: "/agenda", Label: "Agenda", Current: current("/agenda")}, {Href: "/calendars", Label: "Calendars", Current: current("/calendars")}},
+ Actions: []chrome.Action{{Item: estilomenu.Item{Href: "/events/new", Label: "New event"}, Primary: true}},
+ }
+ if !signedIn {
+ bar.Nav, bar.Actions = nil, nil
+ bar.SignIn = &chrome.Link{Href: "/signin?return_to=" + template.URLQueryEscaper(r.URL.RequestURI()), Label: "Sign in"}
+ return bar
+ }
+ if directory != "" {
+ bar.Switcher = &chrome.Switcher{Label: "Oficina apps", AllApps: estilomenu.Item{Href: directory, Label: "All apps"}}
+ }
+ bar.Account = &chrome.Account{TriggerLabel: "Account: " + address, Initials: chrome.Initials("", address), Address: address,
+ Labels: chrome.AccountLabels{SignedInAs: "Signed in as", Appearance: "Appearance", Help: "Help"},
+ Items: []estilomenu.Item{{Href: "/settings", Label: "Settings"}, {Href: "/settings/booking", Label: "Booking pages"}, {Href: "/import", Label: "Import a calendar"}},
+ Appearance: &chrome.AppearanceMenu{Action: "/appearance", Return: r.URL.RequestURI(), Options: chrome.AppearanceOptions(mode, "Light", "Dark", "System")}, Help: []estilomenu.Item{{Href: "/help/open-source", Label: "Open source"}}, SignOut: chrome.SignOut{Action: "/signout", Label: "Sign out"}}
+ return bar
+}
diff --git a/internal/calendar/home_router_test.go b/internal/calendar/home_router_test.go
index a18ab48..94db8f7 100644
--- a/internal/calendar/home_router_test.go
+++ b/internal/calendar/home_router_test.go
@@ -145,7 +145,11 @@ func TestHomeRouterRetainsExistingCalendarIdentity(t *testing.T) {
t.Fatal("proof replaced session")
}
homeRedirect(t, f.do("POST", "/account/home/confirm", ""))
- homeRedirect(t, f.do("POST", "/signout", ""))
+ // connect now preserves this POST across the app → id.* sign-out hop;
+ // a 303 would turn it into a GET and leave the directory session alive.
+ if out := f.do("POST", "/signout", ""); out.Code != http.StatusTemporaryRedirect || out.Header().Get("Location") != homeTestIssuer+"/signout" {
+ t.Fatalf("sign-out = %d %q, want 307 to issuer", out.Code, out.Header().Get("Location"))
+ }
start = homeRedirect(t, f.do("GET", "/signin", ""))
target := homeRedirect(t, f.exchange(t, start, "opaque-home-alice"))
if target != "/" {
diff --git a/internal/calendar/render.go b/internal/calendar/render.go
index ba2b566..7152289 100644
--- a/internal/calendar/render.go
+++ b/internal/calendar/render.go
@@ -14,7 +14,6 @@ import (
"amadan.net/oficina/estilo.git/chrome"
estilomenu "amadan.net/oficina/estilo.git/menu"
"github.com/carlosframework/rastrillo"
- rauth "github.com/carlosframework/rastrillo/auth"
"github.com/carlosframework/rastrillo/sessions"
"github.com/carlosframework/rastrillo/ui"
@@ -190,46 +189,6 @@ func (h *handlers) page(r *http.Request, title string, data any) PageData {
return pd
}
-func (h *handlers) viewer(r *http.Request) (rauth.Identity, bool) {
- if h.viewerFrom != nil {
- return h.viewerFrom(r)
- }
- return viewer(r)
-}
-
-func calendarChrome(r *http.Request, signedIn bool, address string, mode chrome.Mode, title, directory string) chrome.Bar {
- current := func(prefix string) bool {
- return r.URL.Path == prefix || (prefix != "/" && len(r.URL.Path) > len(prefix) && r.URL.Path[:len(prefix)] == prefix)
- }
- bar := chrome.Bar{
- App: chrome.App{Name: "Calendar", Slug: "calendar", Home: "/", Icon: assets.Path("static/brand/icon.svg")},
- Sticky: true, Labels: chrome.BarLabels{Skip: "Skip to content", Menu: "Menu"},
- NavLabel: "Calendar", Nav: []chrome.Link{
- {Href: "/today", Label: "Today", Current: current("/today") || current("/day/") || current("/week/") || current("/month/")},
- {Href: "/agenda", Label: "Agenda", Current: current("/agenda")},
- {Href: "/calendars", Label: "Calendars", Current: current("/calendars")},
- },
- Actions: []chrome.Action{{Item: estilomenu.Item{Href: "/events/new", Label: "New event"}, Primary: true}},
- }
- if !signedIn {
- bar.Nav, bar.Actions = nil, nil
- bar.SignIn = &chrome.Link{Href: "/signin?return_to=" + template.URLQueryEscaper(r.URL.RequestURI()), Label: "Sign in"}
- return bar
- }
- if directory != "" {
- bar.Switcher = &chrome.Switcher{Label: "Oficina apps", AllApps: estilomenu.Item{Href: directory, Label: "All apps"}}
- }
- bar.Account = &chrome.Account{
- TriggerLabel: "Account: " + address, Initials: chrome.Initials("", address), Address: address,
- Labels: chrome.AccountLabels{SignedInAs: "Signed in as", Appearance: "Appearance", Help: "Help"},
- Items: []estilomenu.Item{{Href: "/settings", Label: "Settings"}, {Href: "/settings/booking", Label: "Booking pages"}, {Href: "/import", Label: "Import a calendar"}},
- Appearance: &chrome.AppearanceMenu{Action: "/appearance", Return: r.URL.RequestURI(), Options: chrome.AppearanceOptions(mode, "Light", "Dark", "System")},
- Help: []estilomenu.Item{{Href: "/help/open-source", Label: "Open source"}},
- SignOut: chrome.SignOut{Action: "/signout", Label: "Sign out"},
- }
- return bar
-}
-
// render executes a page into a buffer before anything touches the
// wire: a template error becomes a clean 500 instead of garbage
// appended to a half-written page, and headers stay settable until the
diff --git a/internal/calendar/static/calendar.css b/internal/calendar/static/calendar.css
index b4e934a..bc39c8d 100644
--- a/internal/calendar/static/calendar.css
+++ b/internal/calendar/static/calendar.css
@@ -25,7 +25,7 @@
margin-block-end: var(--rst-sp-4);
}
-[cal-bar] + [rst-seg-tabs] { margin-block: 0 var(--rst-sp-4); }
+[cal-view-tabs] { margin-block: 0 var(--rst-sp-4); }
[cal-bar-spacer] { flex: 1 1 auto; }
diff --git a/internal/calendar/templates/layout.html b/internal/calendar/templates/layout.html
index 9dac19d..e8eb1eb 100644
--- a/internal/calendar/templates/layout.html
+++ b/internal/calendar/templates/layout.html
@@ -34,9 +34,9 @@
</body>
</html>{{end}}
-{{define "calendar-view-tabs"}}<nav rst-seg-tabs aria-label="Calendar view">
+{{define "calendar-view-tabs"}}<div cal-view-tabs><nav rst-seg-tabs aria-label="Calendar view">
<a href="{{.Day}}"{{if eq .View "day"}} aria-current="page"{{end}}>Day</a>
<a href="{{.Week}}"{{if eq .View "week"}} aria-current="page"{{end}}>Week</a>
<a href="{{.Month}}"{{if eq .View "month"}} aria-current="page"{{end}}>Month</a>
<a href="{{.Agenda}}"{{if eq .View "agenda"}} aria-current="page"{{end}}>Agenda</a>
-</nav>{{end}}
+</nav></div>{{end}}