Commit 6fdbe81e72

6fdbe81e72d980cedcb8f426e271d9fe77c7e938

parent: 185f434bf2

Verified · cmc ci/build: success ci/test: success

cmc <hello@cleberg.net> · 2026-09-18 02:09 UTC

web: landing picture, light and dark

Closes #218
e2e/design_test.go +2
@@ -163,6 +163,8 @@ func TestLandingRoutes(t *testing.T) {
163163 `class="button primary" href="/explore">Explore repositories</a>`,
164164 `class="button btn" href="/register">Create an account</a>`,
165165 "web login</code>",
166 "/static/img/mr-dark.png",
167 "<picture>",
166168 } {
167169 if !strings.Contains(body, want) {
168170 t.Errorf("landing lacks %q", want)
internal/httpd/fonts_test.go +34
@@ -1,9 +1,11 @@
11package httpd
22
33import (
4 "io/fs"
45 "net/http"
56 "net/http/httptest"
67 "regexp"
8 "strings"
79 "testing"
810
911 "gitbay.org/gitbay/internal/config"
@@ -43,3 +45,35 @@ func TestStylesheetFontsAreServed(t *testing.T) {
4345 }
4446 }
4547}
48
49// TestLandingImagesAreServed: every file under static/img has a route
50// that answers 200 with an image type and the stylesheet's cache policy.
51func TestLandingImagesAreServed(t *testing.T) {
52 s := New(config.Default(), nil)
53 byPattern := map[string]http.HandlerFunc{}
54 for _, r := range s.Routes() {
55 if r.Method == "GET" {
56 byPattern[r.Pattern] = r.Handler
57 }
58 }
59 entries, err := fs.ReadDir(web.ImageFS, "static/img")
60 if err != nil {
61 t.Fatal(err)
62 }
63 if len(entries) == 0 {
64 t.Fatal("no images embedded")
65 }
66 for _, e := range entries {
67 u := "/static/img/" + e.Name()
68 h, ok := byPattern[u]
69 if !ok {
70 t.Errorf("%s: no route", u)
71 continue
72 }
73 rec := httptest.NewRecorder()
74 h(rec, httptest.NewRequest("GET", u, nil))
75 if rec.Code != http.StatusOK || !strings.HasPrefix(rec.Header().Get("Content-Type"), "image/") {
76 t.Errorf("%s: %d %s", e.Name(), rec.Code, rec.Header().Get("Content-Type"))
77 }
78 }
79}
internal/httpd/routes.go +4
@@ -53,6 +53,10 @@ func (s *Server) Routes() []Route {
5353 for _, f := range fonts {
5454 routes = append(routes, Route{Method: "GET", Pattern: "/static/fonts/" + f.Name(), Handler: s.font})
5555 }
56 images, _ := fs.ReadDir(web.ImageFS, "static/img")
57 for _, f := range images {
58 routes = append(routes, Route{Method: "GET", Pattern: "/static/img/" + f.Name(), Handler: s.image})
59 }
5660 routes = append(routes,
5761 Route{Method: "GET", Pattern: "/favicon.svg", Handler: s.favicon},
5862 Route{Method: "GET", Pattern: "/{owner}", Handler: s.ownerPage},
internal/httpd/web.go +19 −2
@@ -8,6 +8,7 @@ import (
88 "fmt"
99 "hash/fnv"
1010 "io"
11 "io/fs"
1112 "log"
1213 "math"
1314 "os"
@@ -105,6 +106,18 @@ func (s *Server) font(w http.ResponseWriter, r *http.Request) {
105106 w.Write(data)
106107}
107108
109// image serves the embedded landing pictures with the font cache policy.
110func (s *Server) image(w http.ResponseWriter, r *http.Request) {
111 data, err := web.ImageFS.ReadFile("static" + r.URL.Path[len("/static"):])
112 if err != nil {
113 http.NotFound(w, r)
114 return
115 }
116 w.Header().Set("Content-Type", "image/png")
117 w.Header().Set("Cache-Control", "public, max-age=604800, immutable")
118 w.Write(data)
119}
120
108121// notFound renders the designed 404 page with a 404 status. Falls back to
109122// the stock plain-text response if the template fails.
110123func (s *Server) notFound(w http.ResponseWriter, r *http.Request) {
@@ -172,8 +185,12 @@ func (s *Server) index(w http.ResponseWriter, r *http.Request) {
172185}
173186
174187// landingPicture says whether the landing page's screenshot images exist to
175// show. Task 15 replaces this with a check of the embedded images.
176var landingPicture = false
188// show, checked once against the embedded images.
189var landingPicture = func() bool {
190 _, e1 := fs.Stat(web.ImageFS, "static/img/mr-dark.png")
191 _, e2 := fs.Stat(web.ImageFS, "static/img/mr-light.png")
192 return e1 == nil && e2 == nil
193}()
177194
178195func (s *Server) dashboard(w http.ResponseWriter, r *http.Request, viewer store.User) {
179196 pinned, _ := s.st.PinnedRepos(viewer.ID)
internal/web/static/img/mr-dark.png added

Binary file not shown.

internal/web/static/img/mr-light.png added

Binary file not shown.

internal/web/web.go +3
@@ -29,6 +29,9 @@ var FaviconSVG []byte
2929//go:embed static/fonts/*.woff2
3030var FontFS embed.FS
3131
32//go:embed static/img/*.png
33var ImageFS embed.FS
34
3235// version returns the short VCS revision baked into the binary, or "" when
3336// built outside a checkout. Used by the layout footer.
3437var version = sync.OnceValue(func() string {