- Add shared ui_*.html parts (pageHeader, sectionHeader, statusBadge, emptyState) parsed into every template set - Add anatomy lint rules with a shrinking allowlist and screen-coverage check - Add make screens capture harness with contact sheets and baseline diff - Compose member and FedWiki regions server-side so pages arrive complete - Rebuild Domains and Integrations on the parts as pilots
92 lines
3.1 KiB
Go
92 lines
3.1 KiB
Go
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"html/template"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
)
|
|
|
|
// Server-side composition (spec page-anatomy "A page arrives complete";
|
|
// docs/design-system.md §6). A page region that also exists as an HTMX
|
|
// partial, so a mutation can swap it back in, is rendered into the page by
|
|
// the server on first paint through an in-process request to the partial's
|
|
// own route. The browser never fetches first-paint content after load: one
|
|
// response, no spinner, and the partial stays the single source of the
|
|
// region's markup.
|
|
|
|
// Include renders a GET route in-process, with the caller's request context
|
|
// (session, request ID) and headers, and returns its HTML fragment. Core
|
|
// builds one against its router and hands it to integrations through Deps
|
|
// so their card bodies compose the same way.
|
|
type Include func(r *http.Request, path string) (template.HTML, error)
|
|
|
|
// NewInclude returns an Include that dispatches through router. The router
|
|
// is the mux the partial routes are registered on; the middleware stack sits
|
|
// outside it, so an include never re-authenticates: the session already
|
|
// rides the context it is given.
|
|
func NewInclude(router http.Handler) Include {
|
|
return func(r *http.Request, path string) (template.HTML, error) {
|
|
u, err := url.Parse(path)
|
|
if err != nil {
|
|
return "", fmt.Errorf("include %s: %w", path, err)
|
|
}
|
|
req := r.Clone(r.Context())
|
|
req.Method = http.MethodGet
|
|
req.URL = u
|
|
req.RequestURI = path
|
|
req.Body = http.NoBody
|
|
req.ContentLength = 0
|
|
req.Header.Set("HX-Request", "true")
|
|
rec := httptest.NewRecorder()
|
|
router.ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusOK {
|
|
return "", fmt.Errorf("include %s: status %d", path, rec.Code)
|
|
}
|
|
return template.HTML(rec.Body.String()), nil
|
|
}
|
|
}
|
|
|
|
// IncludeOr renders the fragment, or an inline notice naming what could not
|
|
// be loaded, so a failing region never fails the page and never renders
|
|
// blank. A nil Include (a test set without a router) renders nothing.
|
|
func IncludeOr(inc Include, logger *slog.Logger, r *http.Request, path, what string) template.HTML {
|
|
if inc == nil {
|
|
return ""
|
|
}
|
|
out, err := inc(r, path)
|
|
if err != nil {
|
|
if logger != nil {
|
|
logger.Error("page region failed to compose", slog.String("path", path), slog.Any("error", err))
|
|
}
|
|
return template.HTML(`<div class="alert alert-danger" role="alert">Could not load ` + template.HTMLEscapeString(what) + `.</div>`)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// DashboardCardView is a declared card with its body composed for this
|
|
// request. PartialPath stays the swap source for the card's RefreshEvent.
|
|
type DashboardCardView struct {
|
|
DashboardCard
|
|
Body template.HTML
|
|
}
|
|
|
|
// ProductsPageData is the /products page: the shell plus its three regions,
|
|
// composed from their partial routes.
|
|
type ProductsPageData struct {
|
|
Shell Shell
|
|
CSRFToken string
|
|
Entitlements template.HTML
|
|
Plans template.HTML
|
|
Addons template.HTML
|
|
}
|
|
|
|
// BillingPageData is the /billing page: the shell plus the invoices region.
|
|
type BillingPageData struct {
|
|
Shell Shell
|
|
CSRFToken string
|
|
Invoices template.HTML
|
|
}
|