From 237c47f98b70adf3388f56379af45bae85f6a2a5 Mon Sep 17 00:00:00 2001 From: Spencer Delcore Date: Fri, 18 Sep 2026 22:49:29 -0400 Subject: [PATCH 1/2] Improve homepage site discovery --- internal/server/deploy.go | 4 +- internal/server/meta.go | 22 +++++++--- internal/server/meta_test.go | 29 +++++++++++++ internal/server/server.go | 5 +++ internal/web/favicon.svg | 5 +++ internal/web/home.html | 83 +++++++++++++++++++++++++++++++++--- internal/web/web.go | 3 ++ 7 files changed, 140 insertions(+), 11 deletions(-) create mode 100644 internal/server/meta_test.go create mode 100644 internal/web/favicon.svg diff --git a/internal/server/deploy.go b/internal/server/deploy.go index fb9ad2e..563c274 100644 --- a/internal/server/deploy.go +++ b/internal/server/deploy.go @@ -419,9 +419,11 @@ func (s *Server) handleSites(w http.ResponseWriter, r *http.Request) { "updatedAt": updatedAt, "bytes": s.siteBytes(e.Name()), } - views, current, deploys := s.meta.stats(e.Name()) + views, current, createdAt, deploys := s.meta.stats(e.Name()) entry["views"] = views.Total if current != nil { + entry["createdAt"] = createdAt + entry["updatedAt"] = current.Time entry["deployer"] = current.Deployer entry["deploys"] = deploys } diff --git a/internal/server/meta.go b/internal/server/meta.go index 187f79e..9f76ab7 100644 --- a/internal/server/meta.go +++ b/internal/server/meta.go @@ -31,8 +31,9 @@ type viewStats struct { } type siteMeta struct { - Deploys []deployRecord `json:"deploys"` - Views viewStats `json:"views"` + CreatedAt string `json:"createdAt,omitempty"` + Deploys []deployRecord `json:"deploys"` + Views viewStats `json:"views"` } type pendingViews struct { @@ -83,6 +84,12 @@ func (m *metaStore) load(site string) *siteMeta { log.Printf("meta: could not read %s: %v", m.path(site), err) } m.cache[site] = sm + if sm.CreatedAt == "" && len(sm.Deploys) > 0 { + // Legacy metadata did not persist creation separately. Preserve the + // oldest timestamp still available rather than letting it advance again. + sm.CreatedAt = sm.Deploys[0].Time + m.persist(site) + } return sm } @@ -131,13 +138,17 @@ func (m *metaStore) record(site, deployer, source string) int64 { m.mu.Lock() defer m.mu.Unlock() sm := m.load(site) + now := time.Now().UTC().Format(time.RFC3339) seq := int64(1) if n := len(sm.Deploys); n > 0 { seq = sm.Deploys[n-1].Seq + 1 } + if sm.CreatedAt == "" { + sm.CreatedAt = now + } sm.Deploys = append(sm.Deploys, deployRecord{ Seq: seq, - Time: time.Now().UTC().Format(time.RFC3339), + Time: now, Deployer: deployer, Source: source, }) @@ -148,7 +159,7 @@ func (m *metaStore) record(site, deployer, source string) int64 { return seq } -func (m *metaStore) stats(site string) (views viewStats, current *deployRecord, deploys int64) { +func (m *metaStore) stats(site string) (views viewStats, current *deployRecord, createdAt string, deploys int64) { m.mu.Lock() defer m.mu.Unlock() sm := m.load(site) @@ -156,12 +167,13 @@ func (m *metaStore) stats(site string) (views viewStats, current *deployRecord, if p := m.views[site]; p != nil { views.Total += p.count } + createdAt = sm.CreatedAt if n := len(sm.Deploys); n > 0 { rec := sm.Deploys[n-1] current = &rec deploys = rec.Seq } - return views, current, deploys + return views, current, createdAt, deploys } func (m *metaStore) drop(site string) { diff --git a/internal/server/meta_test.go b/internal/server/meta_test.go new file mode 100644 index 0000000..025f043 --- /dev/null +++ b/internal/server/meta_test.go @@ -0,0 +1,29 @@ +package server + +import ( + "testing" +) + +func TestCreationTimeSurvivesDeployHistoryRetention(t *testing.T) { + m, err := newMetaStore(t.TempDir()) + if err != nil { + t.Fatal(err) + } + + m.record("site", "alice", "first") + _, _, createdAt, _ := m.stats("site") + if createdAt == "" { + t.Fatal("creation time was not recorded") + } + + for range maxDeployHistory { + m.record("site", "alice", "update") + } + _, _, got, deploys := m.stats("site") + if got != createdAt { + t.Fatalf("creation time changed after history trimming: got %q, want %q", got, createdAt) + } + if deploys != maxDeployHistory+1 { + t.Fatalf("deploy count = %d, want %d", deploys, maxDeployHistory+1) + } +} diff --git a/internal/server/server.go b/internal/server/server.go index ad87507..256cb1d 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/sdelcore/shared/internal/store" + "github.com/sdelcore/shared/internal/web" ) type Server struct { @@ -78,6 +79,10 @@ func (s *Server) ListenAndServe() error { s.api.ServeHTTP(w, r) case r.URL.Path == "/shared.js": s.handleSharedJS(w, r) + case r.URL.Path == "/favicon.svg" && siteFromHost(r.Host, s.BaseHost) == "": + w.Header().Set("Content-Type", "image/svg+xml") + w.Header().Set("Cache-Control", "public, max-age=86400") + w.Write(web.FaviconSVG) // Base host only: on a site host this path belongs to the site, and // intercepting it would shadow a deployed file of the same name. case r.URL.Path == "/skill.md" && siteFromHost(r.Host, s.BaseHost) == "": diff --git a/internal/web/favicon.svg b/internal/web/favicon.svg new file mode 100644 index 0000000..644d8d0 --- /dev/null +++ b/internal/web/favicon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/internal/web/home.html b/internal/web/home.html index 1f55913..565aa32 100644 --- a/internal/web/home.html +++ b/internal/web/home.html @@ -4,6 +4,7 @@ shared +