···1010 "encoding/json"
1111 "fmt"
1212 "io"
1313+ "sort"
1314 "time"
14151516 "entgo.io/ent/dialect"
···393394 out = append(out, r)
394395 }
395396 return out, rows.Err()
397397+}
398398+399399+// PrimaryLanguages returns the sorted set of "primary" languages observed
400400+// across all enriched repos — i.e., the highest-bytes language per repo,
401401+// deduplicated. Used to populate the language filter dropdown.
402402+func (s *Store) PrimaryLanguages(ctx context.Context) ([]string, error) {
403403+ rows, err := s.db.QueryContext(ctx, `SELECT languages FROM repos WHERE languages IS NOT NULL AND languages != '' AND languages != '{}'`)
404404+ if err != nil {
405405+ return nil, err
406406+ }
407407+ defer rows.Close()
408408+ seen := make(map[string]struct{})
409409+ for rows.Next() {
410410+ var raw string
411411+ if err := rows.Scan(&raw); err != nil {
412412+ return nil, err
413413+ }
414414+ var langs map[string]int64
415415+ if err := json.Unmarshal([]byte(raw), &langs); err != nil {
416416+ continue
417417+ }
418418+ var best string
419419+ var bestN int64
420420+ for k, v := range langs {
421421+ if v > bestN {
422422+ bestN, best = v, k
423423+ }
424424+ }
425425+ if best != "" {
426426+ seen[best] = struct{}{}
427427+ }
428428+ }
429429+ if err := rows.Err(); err != nil {
430430+ return nil, err
431431+ }
432432+ out := make([]string, 0, len(seen))
433433+ for k := range seen {
434434+ out = append(out, k)
435435+ }
436436+ sort.Strings(out)
437437+ return out, nil
396438}
397439398440// RecentRepos returns repos created since `since`, optionally filtered by