coverage~bigbes/sr-ht-doltede3b0bbbeads/beads.go

Coverage
95.8% 23/24 statements
Δ
Blob
eefd1e8
Uncovered L86-L87
1 // Package beads is the one reading of the beads (bd) issue schema a hosted Dolt
2 // database may carry: the table fingerprint, the lane bucketing, the ready rule,
3 // the status categories, the transitive dependency walk, the event humanizer and
4 // the milestone rollup.
5 //
6 // # Why it is its own package
7 //
8 // This reading grew inside web/beads.go, serving one HTML board. It has more
9 // than one consumer now — the web views and the read-only MCP surface — and a
10 // consumer that cannot reach it grows its own copy, which is how two surfaces
11 // start disagreeing quietly about what "ready" or "closed" means. There is one
12 // fingerprint, one ready rule and one row cap on this instance, and they live
13 // here.
14 //
15 // # What it is allowed to know
16 //
17 // Rows in, view model out. This package depends on browse (the row reader) and
18 // the standard library, and on nothing else in the module: no net/http, no
19 // html/template, no core. It renders nothing and it authorizes nothing — by the
20 // time a caller gets here it has already decided that this caller may read this
21 // database. The structs are plain data carrying a few display-derived methods
22 // (PriorityLabel, Pct, SubtaskPct) that the HTML templates call on the dot; no
23 // HTML is built here.
24 //
25 // There is no SQL engine behind any of this: a bare NBS store has no working
26 // set, so every table is read whole (up to Max rows) through the BrowseSession
27 // seam and projected in process.
28 package beads
29
30 import (
31 "context"
32 "strings"
33
34 "sourcecraft.dev/bigbes/sr-ht-dolt/browse"
35 )
36
37 // BrowseSession is the read-only row surface this package reads a database
38 // through. It is declared here, consumer-side (the house style web/deps.go
39 // sets), and names exactly the one method the projections call: everything
40 // below reads whole tables and projects them, so nothing here needs branches,
41 // commits or a table listing. web's larger BrowseSession — and *browse.DB
42 // itself — satisfy it structurally, so a caller hands its own session straight
43 // through.
44 type BrowseSession interface {
45 // Rows reads a page of a table's rows at refStr. A table that does not exist
46 // must report browse.ErrTableNotFound, which readRowsOptional degrades to an
47 // empty page for the tables beads treats as optional.
48 Rows(ctx context.Context, refStr, table string, offset, limit int) (*browse.RowPage, error)
49 }
50
51 // Max caps how many rows of any single table a projection reads. Beads DBs are
52 // modest (hundreds–low thousands of issues); if a table exceeds this the board
53 // notes it is truncated rather than trying to page.
54 const Max = 2000
55
56 // Applies fingerprints a beads DB: both an "issues" and a "dependencies" table
57 // present, and "issues" carrying at least id + status columns (a cheap guard
58 // against an unrelated schema that happens to reuse those two table names).
59 108 func Applies(tables []browse.TableInfo) bool {
60 108 var haveIssues, haveDeps, haveID, haveStatus bool
61 317 for _, t := range tables {
62 317 switch t.Name {
63 105 case "issues":
64 105 haveIssues = true
65 209 for _, c := range t.Columns {
66 209 switch c.Name {
67 105 case "id":
68 105 haveID = true
69 104 case "status":
70 104 haveStatus = true
71 }
72 }
73 104 case "dependencies":
74 104 haveDeps = true
75 }
76 }
77 108 return haveIssues && haveDeps && haveID && haveStatus
78 }
79
80 // statusCategory maps a status name to one of open / in_progress / closed. It
81 // prefers the custom_statuses lookup and falls back to name heuristics when the
82 // status is unknown there (or the table was empty).
83 22852 func statusCategory(status string, catByStatus map[string]string) string {
84 22852 s := strings.ToLower(strings.TrimSpace(status))
85 22852 if s == "" {
86 0 return "open"
87 0 }
88 22852 if cat, ok := catByStatus[s]; ok && cat != "" {
89 14615 switch cat {
90 14615 case "in_progress", "closed", "open":
91 14615 return cat
92 }
93 }
94 8237 switch {
95 2021 case strings.Contains(s, "progress"), strings.Contains(s, "doing"), strings.Contains(s, "active"), s == "wip":
96 2021 return "in_progress"
97 4024 case strings.Contains(s, "close"), strings.Contains(s, "done"), strings.Contains(s, "resolved"), strings.Contains(s, "complete"):
98 4024 return "closed"
99 2192 default:
100 2192 return "open"
101 }
102 }
103
104 // laneForCategory returns the lane display name and accent for a status
105 // category (used by the detail pane; the board buckets inline because it also
106 // needs the blocked signal).
107 20 func laneForCategory(cat string) (name, accent string) {
108 20 switch cat {
109 2 case "closed":
110 2 return "Past Stand", "#868e96"
111 2 case "in_progress":
112 2 return "Rolling", "#c9930a"
113 16 default:
114 16 return "Lined Up", "#2f9e44"
115 }
116 }