oficina / calendar Public

Review nits from the shared-chrome and rastrillo-path reviews

Fixes for the non-blocking findings on the merged branches (see this
branch's discussion). The coordinating session checked them, on Paul's
standing instruction to roll in fixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Squashed from chore/review-nits into main.
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 41641ad4ff5b56c3eb12fd891aa0aefa3c28def0 parent 85aaf95
3 files changed, +109 −5
  • internal/calendar/static/calendar.css +7 −0
  • internal/calendartest/quickadd_browser_test.go +87 −0
  • internal/calendartest/schedule_recovery_test.go +15 −5

CI passed — run details

CI log
=== vet ===
go vet ./...
go vet -tags browser ./internal/calendartest/
=== fmt ===
=== test ===
go test ./...
?   	amadan.net/oficina/calendar/cmd/calendar	[no test files]
ok  	amadan.net/oficina/calendar/internal/calendar	4.857s
?   	amadan.net/oficina/calendar/internal/calendar/icons	[no test files]
?   	amadan.net/oficina/calendar/internal/calendar/seal	[no test files]
ok  	amadan.net/oficina/calendar/internal/calendartest	2.341s
ok  	amadan.net/oficina/calendar/internal/guardstest	0.055s
?   	amadan.net/oficina/calendar/scripts/writeestilo	[no test files]
=== migration-check ===
go tool rastrillo migration check
rastrillo migration check: models and migrations agree
diff --git a/internal/calendar/static/calendar.css b/internal/calendar/static/calendar.css
index 9868d72..f1a207c 100644
--- a/internal/calendar/static/calendar.css
+++ b/internal/calendar/static/calendar.css
@@ -472,6 +472,12 @@ html { scroll-padding-block-start: var(--estilo-bar-block-size); }
* shared-chrome spec §11), not a hand-built 6px and shadow of its own. */
border-radius: var(--estilo-float-radius);
box-shadow: var(--rst-shadow-pop);
+ /* Border-box, here and on the textarea below: both are width: 100%
+ * on a phone, and content-box added the padding and border on top,
+ * so the open panel ran ~15px past the right edge at 390px and the
+ * page scrolled sideways. TestQuickAddOnANarrowScreen (make browser)
+ * measures it. */
+ box-sizing: border-box;
/* Anchored to the END edge, so the panel opens back across the page
* instead of off the side of it. The box sits at the far right of the
* header, beside New event: anchored at the start edge a 30rem panel
@@ -490,6 +496,7 @@ html { scroll-padding-block-start: var(--estilo-bar-block-size); }
background: var(--rst-bg);
border: 1px solid var(--rst-line);
border-radius: var(--rst-radius-sm);
+ box-sizing: border-box;
color: var(--rst-text);
display: block;
font: inherit;
diff --git a/internal/calendartest/quickadd_browser_test.go b/internal/calendartest/quickadd_browser_test.go
new file mode 100644
index 0000000..d7f3fcf
--- /dev/null
+++ b/internal/calendartest/quickadd_browser_test.go
@@ -0,0 +1,87 @@
+//go:build browser
+
+package calendartest
+
+import (
+ "context"
+ "io"
+ "log/slog"
+ "net/http"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "amadan.net/rastrillo/rastrillo/harness"
+ "github.com/chromedp/cdproto/emulation"
+ "github.com/chromedp/cdproto/network"
+ "github.com/chromedp/chromedp"
+
+ calendar "amadan.net/oficina/calendar/internal/calendar"
+)
+
+// TestQuickAddOnANarrowScreen: on a phone the quick-add panel stops
+// floating and drops into the page (calendar.css, max-width 40rem), and
+// at 390px it ran about 15px past the right edge of the screen — the
+// textarea's and the Add button's right ends were off the side, and the
+// page scrolled sideways. Every string check in the Go suite passes a
+// panel like that. This opens it at 320 and 390 and fails if anything
+// inside it reaches past the viewport, or the page grows wider than the
+// screen.
+func TestQuickAddOnANarrowScreen(t *testing.T) {
+ var app *calendar.App
+ rig := harness.New(t, func(origin string) http.Handler {
+ a, err := calendar.New(calendar.Config{
+ DBPath: filepath.Join(t.TempDir(), "calendar.db"),
+ Env: "development",
+ Origin: origin,
+ Logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
+ })
+ if err != nil {
+ t.Fatalf("calendar.New: %v", err)
+ }
+ t.Cleanup(func() { a.Close() })
+ app = a
+ return a.Handler()
+ })
+ session := signInOverHTTP(t, app, rig.Origin, "host@example.com")
+
+ for _, width := range []int64{320, 390} {
+ var problems []string
+ ctx, cancel := context.WithTimeout(rig.Context(), 20*time.Second)
+ err := chromedp.Run(ctx,
+ network.SetCookie(session.Name, session.Value).WithURL(rig.Origin+"/").
+ WithHTTPOnly(session.HttpOnly).WithSameSite(network.CookieSameSiteLax),
+ emulation.SetDeviceMetricsOverride(width, 844, 1, true),
+ chromedp.Navigate(rig.Origin+"/"),
+ chromedp.WaitVisible(`[cal-quick] > summary`, chromedp.ByQuery),
+ chromedp.Click(`[cal-quick] > summary`, chromedp.ByQuery),
+ chromedp.WaitVisible(`[cal-quick-text]`, chromedp.ByQuery),
+ chromedp.Evaluate(quickAddProblems, &problems),
+ )
+ cancel()
+ if err != nil {
+ t.Fatalf("%dpx: %v", width, err)
+ }
+ for _, p := range problems {
+ t.Errorf("%dpx, the open quick-add panel: %s", width, p)
+ }
+ }
+}
+
+// quickAddProblems lists every visible element of the open quick-add
+// panel that reaches past either side of the viewport, and the page
+// itself if it scrolls sideways.
+const quickAddProblems = `(() => {
+ const vw = document.documentElement.clientWidth, out = []
+ const els = [document.querySelector('[cal-quick]'), ...document.querySelectorAll('[cal-quick] *')]
+ for (const el of els) {
+ const r = el.getBoundingClientRect()
+ if (r.width === 0 && r.height === 0) continue
+ if (r.right > vw + 0.5 || r.left < -0.5) {
+ const name = el.tagName.toLowerCase() + [...el.attributes].filter(a => a.name.startsWith('cal-')).map(a => '[' + a.name + ']').join('')
+ out.push(name + ' spans ' + Math.round(r.left) + '-' + Math.round(r.right) + ' in a ' + vw + 'px viewport')
+ }
+ }
+ if (document.documentElement.scrollWidth > vw) out.push('the page is ' + document.documentElement.scrollWidth + 'px wide in a ' + vw + 'px viewport')
+ return out
+})()`
diff --git a/internal/calendartest/schedule_recovery_test.go b/internal/calendartest/schedule_recovery_test.go
index 4c2699d..1e1f420 100644
--- a/internal/calendartest/schedule_recovery_test.go
+++ b/internal/calendartest/schedule_recovery_test.go
@@ -9,16 +9,26 @@ import (
calendar "amadan.net/oficina/calendar/internal/calendar"
)
+// The work is in the database before the failing boot, as it is when an
+// instance restarts. It used to be inserted straight into the database
+// after boot, which raced the retry loop: when the first retry ran before
+// the INSERT it found nothing due, succeeded, and nothing woke it again
+// (a raw INSERT bypasses the request middleware that wakes it), so about
+// one run in ten failed "timer was not armed".
func TestTransientControlFailureAtBootRecovers(t *testing.T) {
control := controlForTest(t)
- control.mu.Lock()
- control.fail = true
- control.mu.Unlock()
- app := calendar.NewForTest(t, t.TempDir())
+ dir := t.TempDir()
+ seed := calendar.NewForTest(t, dir)
due := time.Now().Add(time.Hour).Unix()
- if _, err := app.DBForTest().Exec(`INSERT INTO reminder_queues (reminder_id,occurrence_key,due_at,attempts,late_by_sec) VALUES (1,'boot-retry',?,0,0)`, due); err != nil {
+ if _, err := seed.DBForTest().Exec(`INSERT INTO reminder_queues (reminder_id,occurrence_key,due_at,attempts,late_by_sec) VALUES (1,'boot-retry',?,0,0)`, due); err != nil {
t.Fatal(err)
}
+ seed.Close()
+ control.mu.Lock()
+ control.at = time.Time{}
+ control.fail = true
+ control.mu.Unlock()
+ calendar.NewForTest(t, dir)
control.mu.Lock()
control.fail = false
control.mu.Unlock()