nandi/gleanpublic Fork 0
68224be
Commits
Clone
git clone https://git.rickub.com/nandi/glean.git
git clone ssh://git@rickub.com/nandi/glean.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Improve feeds page loading and remove user rec thereUnverified

Julien Robert committed 2026-06-04T09:40:52+02:00 Browse files
68224be parent: 71d0cbb
modified internal/server/feeds_handler.go +10 -68
@@ -10,10 +10,8 @@ import (
1010 "golang.org/x/sync/errgroup"
1111
1212 "pkg.rbrt.fr/glean/internal/atproto"
13- "pkg.rbrt.fr/glean/internal/cluster"
1413 "pkg.rbrt.fr/glean/internal/db"
1514 "pkg.rbrt.fr/glean/internal/feed"
16- "pkg.rbrt.fr/glean/internal/feedback"
1715 )
1816
1917 func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) {
@@ -26,8 +24,6 @@ func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) {
2624 var (
2725 subs []*db.Subscription
2826 subCount int
29- feedRecs []*cluster.FeedRecommendation
30- peopleRecs []*cluster.PersonRecommendation
3127 deadFeeds []*db.Feed
3228 categories []string
3329 )
@@ -58,24 +54,6 @@ func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) {
5854 return nil
5955 })
6056
61- g.Go(func() error {
62- var err error
63- feedRecs, err = s.engine.GetFeedRecommendations(gCtx, user.DID, 6)
64- if err != nil {
65- s.logger.Warn("failed to get feed recommendations", "error", err, "did", user.DID)
66- }
67- return nil
68- })
69-
70- g.Go(func() error {
71- var err error
72- peopleRecs, err = s.engine.GetPeopleRecommendations(gCtx, user.DID, 6)
73- if err != nil {
74- s.logger.Warn("failed to get people recommendations", "error", err, "did", user.DID)
75- }
76- return nil
77- })
78-
7957 g.Go(func() error {
8058 var err error
8159 deadFeeds, err = s.dbs.Articles.ListDeadFeeds(gCtx, user.DID, 7)
@@ -95,55 +73,19 @@ func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) {
9573 })
9674
9775 if err := g.Wait(); err != nil {
98- s.logger.Warn("feeds phase 1 error", "error", err, "did", user.DID)
99- }
100-
101- g2, gCtx2 := errgroup.WithContext(ctx)
102-
103- g2.Go(func() error {
104- resolvePeopleHandles(gCtx2, peopleRecs)
105- return nil
106- })
107-
108- g2.Go(func() error {
109- if len(feedRecs) > 0 {
110- impressions := make([]feedback.Impression, len(feedRecs))
111- for i, rec := range feedRecs {
112- impressions[i] = feedback.Impression{TargetType: "feed", TargetID: rec.FeedURL}
113- }
114- if err := s.feedback.RecordImpressions(gCtx2, user.DID, impressions); err != nil {
115- s.logger.Warn("failed to record impressions", "error", err)
116- }
117- }
118- return nil
119- })
120-
121- if err := g2.Wait(); err != nil {
122- s.logger.Warn("feeds phase 2 error", "error", err, "did", user.DID)
123- }
124-
125- var followedPeople, discoverPeople []*cluster.PersonRecommendation
126- for _, p := range peopleRecs {
127- if p.IsFollowed {
128- followedPeople = append(followedPeople, p)
129- } else {
130- discoverPeople = append(discoverPeople, p)
131- }
76+ s.logger.Warn("feeds error", "error", err, "did", user.DID)
13277 }
13378
13479 s.render(w, r, "feeds.html", map[string]any{
135- "User": user,
136- "Subscriptions": subs,
137- "SubscriptionCount": subCount,
138- "Categories": categories,
139- "Category": category,
140- "FeedRecommendations": feedRecs,
141- "FollowedPeople": followedPeople,
142- "DiscoverPeople": discoverPeople,
143- "DeadFeeds": deadFeeds,
144- "Page": page,
145- "BaseURL": "/feeds",
146- "QueryParams": buildQueryParams(map[string]string{"category": category}),
80+ "User": user,
81+ "Subscriptions": subs,
82+ "SubscriptionCount": subCount,
83+ "Categories": categories,
84+ "Category": category,
85+ "DeadFeeds": deadFeeds,
86+ "Page": page,
87+ "BaseURL": "/feeds",
88+ "QueryParams": buildQueryParams(map[string]string{"category": category}),
14789 })
14890 }
14991
@@ -10,10 +10,8 @@ import (
10 "golang.org/x/sync/errgroup"10 "golang.org/x/sync/errgroup"
11 11
12 "pkg.rbrt.fr/glean/internal/atproto"12 "pkg.rbrt.fr/glean/internal/atproto"
13- "pkg.rbrt.fr/glean/internal/cluster"
14 "pkg.rbrt.fr/glean/internal/db"13 "pkg.rbrt.fr/glean/internal/db"
15 "pkg.rbrt.fr/glean/internal/feed"14 "pkg.rbrt.fr/glean/internal/feed"
16- "pkg.rbrt.fr/glean/internal/feedback"
17 )15 )
18 16
19 func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) {17 func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) {
@@ -26,8 +24,6 @@ func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) {
26 var (24 var (
27 subs []*db.Subscription25 subs []*db.Subscription
28 subCount int26 subCount int
29- feedRecs []*cluster.FeedRecommendation
30- peopleRecs []*cluster.PersonRecommendation
31 deadFeeds []*db.Feed27 deadFeeds []*db.Feed
32 categories []string28 categories []string
33 )29 )
@@ -58,24 +54,6 @@ func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) {
58 return nil54 return nil
59 })55 })
60 56
61- g.Go(func() error {
62- var err error
63- feedRecs, err = s.engine.GetFeedRecommendations(gCtx, user.DID, 6)
64- if err != nil {
65- s.logger.Warn("failed to get feed recommendations", "error", err, "did", user.DID)
66- }
67- return nil
68- })
69-
70- g.Go(func() error {
71- var err error
72- peopleRecs, err = s.engine.GetPeopleRecommendations(gCtx, user.DID, 6)
73- if err != nil {
74- s.logger.Warn("failed to get people recommendations", "error", err, "did", user.DID)
75- }
76- return nil
77- })
78-
79 g.Go(func() error {57 g.Go(func() error {
80 var err error58 var err error
81 deadFeeds, err = s.dbs.Articles.ListDeadFeeds(gCtx, user.DID, 7)59 deadFeeds, err = s.dbs.Articles.ListDeadFeeds(gCtx, user.DID, 7)
@@ -95,55 +73,19 @@ func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) {
95 })73 })
96 74
97 if err := g.Wait(); err != nil {75 if err := g.Wait(); err != nil {
98- s.logger.Warn("feeds phase 1 error", "error", err, "did", user.DID)76+ s.logger.Warn("feeds error", "error", err, "did", user.DID)
99- }
100-
101- g2, gCtx2 := errgroup.WithContext(ctx)
102-
103- g2.Go(func() error {
104- resolvePeopleHandles(gCtx2, peopleRecs)
105- return nil
106- })
107-
108- g2.Go(func() error {
109- if len(feedRecs) > 0 {
110- impressions := make([]feedback.Impression, len(feedRecs))
111- for i, rec := range feedRecs {
112- impressions[i] = feedback.Impression{TargetType: "feed", TargetID: rec.FeedURL}
113- }
114- if err := s.feedback.RecordImpressions(gCtx2, user.DID, impressions); err != nil {
115- s.logger.Warn("failed to record impressions", "error", err)
116- }
117- }
118- return nil
119- })
120-
121- if err := g2.Wait(); err != nil {
122- s.logger.Warn("feeds phase 2 error", "error", err, "did", user.DID)
123- }
124-
125- var followedPeople, discoverPeople []*cluster.PersonRecommendation
126- for _, p := range peopleRecs {
127- if p.IsFollowed {
128- followedPeople = append(followedPeople, p)
129- } else {
130- discoverPeople = append(discoverPeople, p)
131- }
132 }77 }
133 78
134 s.render(w, r, "feeds.html", map[string]any{79 s.render(w, r, "feeds.html", map[string]any{
135- "User": user,80+ "User": user,
136- "Subscriptions": subs,81+ "Subscriptions": subs,
137- "SubscriptionCount": subCount,82+ "SubscriptionCount": subCount,
138- "Categories": categories,83+ "Categories": categories,
139- "Category": category,84+ "Category": category,
140- "FeedRecommendations": feedRecs,85+ "DeadFeeds": deadFeeds,
141- "FollowedPeople": followedPeople,86+ "Page": page,
142- "DiscoverPeople": discoverPeople,87+ "BaseURL": "/feeds",
143- "DeadFeeds": deadFeeds,88+ "QueryParams": buildQueryParams(map[string]string{"category": category}),
144- "Page": page,
145- "BaseURL": "/feeds",
146- "QueryParams": buildQueryParams(map[string]string{"category": category}),
147 })89 })
148 }90 }
149 91
modified internal/server/server.go +3 -3
@@ -168,9 +168,6 @@ func (s *Server) setupRoutes() {
168168 s.router.Route("/dashboard", func(r chi.Router) {
169169 r.Use(s.requireAuth)
170170 r.Get("/", s.handleDashboard)
171- r.Get("/article-recommendations", s.handleArticleRecommendations)
172- r.Get("/feed-recommendations", s.handleFeedRecommendations)
173- r.Get("/people-recommendations", s.handlePeopleRecommendations)
174171 })
175172
176173 s.router.Route("/feeds", func(r chi.Router) {
@@ -217,6 +214,9 @@ func (s *Server) setupRoutes() {
217214
218215 s.router.Route("/recs", func(r chi.Router) {
219216 r.Use(s.requireAuth)
217+ r.Get("/articles", s.handleArticleRecommendations)
218+ r.Get("/feeds", s.handleFeedRecommendations)
219+ r.Get("/people", s.handlePeopleRecommendations)
220220 r.Post("/dismiss-feed", s.handleDismissFeedRecommendation)
221221 r.Post("/dismiss-article", s.handleDismissArticleRecommendation)
222222 r.Post("/dismiss-person", s.handleDismissPersonRecommendation)
@@ -168,9 +168,6 @@ func (s *Server) setupRoutes() {
168 s.router.Route("/dashboard", func(r chi.Router) {168 s.router.Route("/dashboard", func(r chi.Router) {
169 r.Use(s.requireAuth)169 r.Use(s.requireAuth)
170 r.Get("/", s.handleDashboard)170 r.Get("/", s.handleDashboard)
171- r.Get("/article-recommendations", s.handleArticleRecommendations)
172- r.Get("/feed-recommendations", s.handleFeedRecommendations)
173- r.Get("/people-recommendations", s.handlePeopleRecommendations)
174 })171 })
175 172
176 s.router.Route("/feeds", func(r chi.Router) {173 s.router.Route("/feeds", func(r chi.Router) {
@@ -217,6 +214,9 @@ func (s *Server) setupRoutes() {
217 214
218 s.router.Route("/recs", func(r chi.Router) {215 s.router.Route("/recs", func(r chi.Router) {
219 r.Use(s.requireAuth)216 r.Use(s.requireAuth)
217+ r.Get("/articles", s.handleArticleRecommendations)
218+ r.Get("/feeds", s.handleFeedRecommendations)
219+ r.Get("/people", s.handlePeopleRecommendations)
220 r.Post("/dismiss-feed", s.handleDismissFeedRecommendation)220 r.Post("/dismiss-feed", s.handleDismissFeedRecommendation)
221 r.Post("/dismiss-article", s.handleDismissArticleRecommendation)221 r.Post("/dismiss-article", s.handleDismissArticleRecommendation)
222 r.Post("/dismiss-person", s.handleDismissPersonRecommendation)222 r.Post("/dismiss-person", s.handleDismissPersonRecommendation)
modified internal/tmpl/dashboard.html +4 -4
@@ -28,7 +28,7 @@
2828 </div>
2929 {{else if .Articles}}
3030 {{if gt .SubscriptionCount 0}}
31-<div id="article-recommendations" hx-get="/dashboard/article-recommendations" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#article-recommendations">
31+<div id="article-recommendations" hx-get="/recs/articles" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#article-recommendations">
3232 <div class="mb-8">
3333 <h2 class="text-lg font-semibold text-spot-text mb-4">Recommended for you</h2>
3434 <div class="animate-pulse space-y-3">
@@ -90,7 +90,7 @@
9090 </div>
9191 {{else}}
9292 {{if gt .SubscriptionCount 0}}
93-<div id="article-recommendations" hx-get="/dashboard/article-recommendations" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#article-recommendations">
93+<div id="article-recommendations" hx-get="/recs/articles" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#article-recommendations">
9494 <div class="mb-8">
9595 <h2 class="text-lg font-semibold text-spot-text mb-4">Recommended for you</h2>
9696 <div class="animate-pulse space-y-3">
@@ -134,7 +134,7 @@
134134 </div>
135135 {{end}}
136136
137-<div id="people-recommendations" hx-get="/dashboard/people-recommendations" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#people-recommendations">
137+<div id="people-recommendations" hx-get="/recs/people" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#people-recommendations">
138138 <div class="mb-8">
139139 <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
140140 <div>
@@ -172,7 +172,7 @@
172172 </div>
173173 </div>
174174
175-<div id="feed-recommendations" hx-get="/dashboard/feed-recommendations" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#feed-recommendations">
175+<div id="feed-recommendations" hx-get="/recs/feeds" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#feed-recommendations">
176176 <div class="mb-8">
177177 <h2 class="text-lg font-semibold text-spot-text mb-4">Recommended feeds</h2>
178178 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
@@ -28,7 +28,7 @@
28 </div>28 </div>
29 {{else if .Articles}}29 {{else if .Articles}}
30 {{if gt .SubscriptionCount 0}}30 {{if gt .SubscriptionCount 0}}
31-<div id="article-recommendations" hx-get="/dashboard/article-recommendations" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#article-recommendations">31+<div id="article-recommendations" hx-get="/recs/articles" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#article-recommendations">
32 <div class="mb-8">32 <div class="mb-8">
33 <h2 class="text-lg font-semibold text-spot-text mb-4">Recommended for you</h2>33 <h2 class="text-lg font-semibold text-spot-text mb-4">Recommended for you</h2>
34 <div class="animate-pulse space-y-3">34 <div class="animate-pulse space-y-3">
@@ -90,7 +90,7 @@
90 </div>90 </div>
91 {{else}}91 {{else}}
92 {{if gt .SubscriptionCount 0}}92 {{if gt .SubscriptionCount 0}}
93-<div id="article-recommendations" hx-get="/dashboard/article-recommendations" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#article-recommendations">93+<div id="article-recommendations" hx-get="/recs/articles" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#article-recommendations">
94 <div class="mb-8">94 <div class="mb-8">
95 <h2 class="text-lg font-semibold text-spot-text mb-4">Recommended for you</h2>95 <h2 class="text-lg font-semibold text-spot-text mb-4">Recommended for you</h2>
96 <div class="animate-pulse space-y-3">96 <div class="animate-pulse space-y-3">
@@ -134,7 +134,7 @@
134 </div>134 </div>
135 {{end}}135 {{end}}
136 136
137-<div id="people-recommendations" hx-get="/dashboard/people-recommendations" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#people-recommendations">137+<div id="people-recommendations" hx-get="/recs/people" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#people-recommendations">
138 <div class="mb-8">138 <div class="mb-8">
139 <div class="grid grid-cols-1 md:grid-cols-2 gap-6">139 <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
140 <div>140 <div>
@@ -172,7 +172,7 @@
172 </div>172 </div>
173 </div>173 </div>
174 174
175-<div id="feed-recommendations" hx-get="/dashboard/feed-recommendations" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#feed-recommendations">175+<div id="feed-recommendations" hx-get="/recs/feeds" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#feed-recommendations">
176 <div class="mb-8">176 <div class="mb-8">
177 <h2 class="text-lg font-semibold text-spot-text mb-4">Recommended feeds</h2>177 <h2 class="text-lg font-semibold text-spot-text mb-4">Recommended feeds</h2>
178 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">178 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
modified internal/tmpl/feeds.html +23 -36
@@ -21,21 +21,31 @@ function gleanToggleEdit(id){var i=document.getElementById('feed-edit-'+id);if(i
2121
2222 {{template "dead-feeds.html" (dict "DeadFeeds" .DeadFeeds "CSRFToken" .CSRFToken)}}
2323
24-{{if .FeedRecommendations}}
25-<div class="mb-6">
26- <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide mb-3">Recommended feeds</h2>
27- <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
28- {{range .FeedRecommendations}}
29- {{template "recommendation-feed-card.html" (dict "title" .Title "feed_url" .FeedURL "description" .Description "favicon_url" .FaviconURL "subscriber_count" .SubscriberCount "CSRFToken" $.CSRFToken)}}
30- {{end}}
24+<div id="feed-recommendations" hx-get="/recs/feeds" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#feed-recommendations">
25+ <div class="mb-6">
26+ <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide mb-3">Recommended feeds</h2>
27+ <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
28+ <div class="animate-pulse bg-spot-surface rounded-xl p-3.5">
29+ <div class="flex items-center gap-2.5">
30+ <div class="w-5 h-5 bg-spot-hover rounded"></div>
31+ <div class="flex-1 space-y-2">
32+ <div class="h-4 bg-spot-hover rounded w-2/3"></div>
33+ <div class="h-3 bg-spot-hover rounded w-full"></div>
34+ </div>
35+ </div>
36+ </div>
37+ <div class="animate-pulse bg-spot-surface rounded-xl p-3.5">
38+ <div class="flex items-center gap-2.5">
39+ <div class="w-5 h-5 bg-spot-hover rounded"></div>
40+ <div class="flex-1 space-y-2">
41+ <div class="h-4 bg-spot-hover rounded w-1/2"></div>
42+ <div class="h-3 bg-spot-hover rounded w-4/5"></div>
43+ </div>
44+ </div>
45+ </div>
46+ </div>
3147 </div>
3248 </div>
33-{{else if .SubscriptionCount}}
34-<div class="mb-6">
35- <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide mb-3">Recommended feeds</h2>
36- {{template "empty-state.html" (dict "icon" "icon-rss" "size" "compact" "title" "No feed suggestions yet" "subtitle" "We'll suggest feeds as you read more.")}}
37-</div>
38-{{end}}
3949
4050 <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
4151 <div class="lg:col-span-2">
@@ -87,29 +97,6 @@ function gleanToggleEdit(id){var i=document.getElementById('feed-edit-'+id);if(i
8797 </div>
8898 </div>
8999
90- {{if or .FollowedPeople .DiscoverPeople}}
91- <div>
92- {{if .FollowedPeople}}
93- <h2 class="text-lg font-semibold text-spot-text mb-4">Your network</h2>
94- <div class="space-y-3">
95- {{range .FollowedPeople}}
96- {{template "profile-card.html" .}}
97- {{end}}
98- </div>
99- {{end}}
100- {{if .DiscoverPeople}}
101- <h2 class="text-lg font-semibold text-spot-text mb-4 {{if .FollowedPeople}}mt-8{{end}}">Discover new readers</h2>
102- <div class="space-y-3">
103- {{range .DiscoverPeople}}
104- {{template "profile-card.html" .}}
105- {{end}}
106- </div>
107- {{end}}
108- </div>
109- {{else}}
110- {{template "empty-state.html" (dict "icon" "icon-people" "size" "compact" "title" "No connections yet" "subtitle" "Follow readers on the network to see them here.")}}
111- {{end}}
112-
113100 {{if .Subscriptions}}
114101 <form hx-post="/feeds/clear" hx-confirm="Are you sure you want to unsubscribe from ALL feeds? This cannot be undone.">
115102 {{csrfInput .CSRFToken}}
@@ -21,21 +21,31 @@ function gleanToggleEdit(id){var i=document.getElementById('feed-edit-'+id);if(i
21 21
22 {{template "dead-feeds.html" (dict "DeadFeeds" .DeadFeeds "CSRFToken" .CSRFToken)}}22 {{template "dead-feeds.html" (dict "DeadFeeds" .DeadFeeds "CSRFToken" .CSRFToken)}}
23 23
24-{{if .FeedRecommendations}}24+<div id="feed-recommendations" hx-get="/recs/feeds" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#feed-recommendations">
25-<div class="mb-6">25+ <div class="mb-6">
26- <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide mb-3">Recommended feeds</h2>26+ <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide mb-3">Recommended feeds</h2>
27- <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">27+ <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
28- {{range .FeedRecommendations}}28+ <div class="animate-pulse bg-spot-surface rounded-xl p-3.5">
29- {{template "recommendation-feed-card.html" (dict "title" .Title "feed_url" .FeedURL "description" .Description "favicon_url" .FaviconURL "subscriber_count" .SubscriberCount "CSRFToken" $.CSRFToken)}}29+ <div class="flex items-center gap-2.5">
30- {{end}}30+ <div class="w-5 h-5 bg-spot-hover rounded"></div>
31+ <div class="flex-1 space-y-2">
32+ <div class="h-4 bg-spot-hover rounded w-2/3"></div>
33+ <div class="h-3 bg-spot-hover rounded w-full"></div>
34+ </div>
35+ </div>
36+ </div>
37+ <div class="animate-pulse bg-spot-surface rounded-xl p-3.5">
38+ <div class="flex items-center gap-2.5">
39+ <div class="w-5 h-5 bg-spot-hover rounded"></div>
40+ <div class="flex-1 space-y-2">
41+ <div class="h-4 bg-spot-hover rounded w-1/2"></div>
42+ <div class="h-3 bg-spot-hover rounded w-4/5"></div>
43+ </div>
44+ </div>
45+ </div>
46+ </div>
31 </div>47 </div>
32 </div>48 </div>
33-{{else if .SubscriptionCount}}
34-<div class="mb-6">
35- <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide mb-3">Recommended feeds</h2>
36- {{template "empty-state.html" (dict "icon" "icon-rss" "size" "compact" "title" "No feed suggestions yet" "subtitle" "We'll suggest feeds as you read more.")}}
37-</div>
38-{{end}}
39 49
40 <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">50 <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
41 <div class="lg:col-span-2">51 <div class="lg:col-span-2">
@@ -87,29 +97,6 @@ function gleanToggleEdit(id){var i=document.getElementById('feed-edit-'+id);if(i
87 </div>97 </div>
88 </div>98 </div>
89 99
90- {{if or .FollowedPeople .DiscoverPeople}}
91- <div>
92- {{if .FollowedPeople}}
93- <h2 class="text-lg font-semibold text-spot-text mb-4">Your network</h2>
94- <div class="space-y-3">
95- {{range .FollowedPeople}}
96- {{template "profile-card.html" .}}
97- {{end}}
98- </div>
99- {{end}}
100- {{if .DiscoverPeople}}
101- <h2 class="text-lg font-semibold text-spot-text mb-4 {{if .FollowedPeople}}mt-8{{end}}">Discover new readers</h2>
102- <div class="space-y-3">
103- {{range .DiscoverPeople}}
104- {{template "profile-card.html" .}}
105- {{end}}
106- </div>
107- {{end}}
108- </div>
109- {{else}}
110- {{template "empty-state.html" (dict "icon" "icon-people" "size" "compact" "title" "No connections yet" "subtitle" "Follow readers on the network to see them here.")}}
111- {{end}}
112-
113 {{if .Subscriptions}}100 {{if .Subscriptions}}
114 <form hx-post="/feeds/clear" hx-confirm="Are you sure you want to unsubscribe from ALL feeds? This cannot be undone.">101 <form hx-post="/feeds/clear" hx-confirm="Are you sure you want to unsubscribe from ALL feeds? This cannot be undone.">
115 {{csrfInput .CSRFToken}}102 {{csrfInput .CSRFToken}}