M beads/beads_test.go => beads/beads_test.go +4 -4
@@ 120,7 120,7 @@ func beadsFixture() *fakeSession {
}
// laneBySlug finds a lane in a built board by its slug.
-func laneBySlug(d *BeadsData, slug string) *BeadsLane {
+func laneBySlug(d *Data, slug string) *Lane {
for i := range d.Lanes {
if d.Lanes[i].Slug == slug {
return &d.Lanes[i]
@@ 130,7 130,7 @@ func laneBySlug(d *BeadsData, slug string) *BeadsLane {
}
// cardIDs lists the ids of a lane's cards.
-func cardIDs(l *BeadsLane) []string {
+func cardIDs(l *Lane) []string {
if l == nil {
return nil
}
@@ 177,7 177,7 @@ func TestBeadsBuildBoardLanes(t *testing.T) {
assert.Equal(t, want, cardIDs(laneBySlug(d, slug)), "lane %s", slug)
}
- assert.Equal(t, BeadsCounts{Rolling: 1, LinedUp: 1, Stalled: 1, PastStand: 1, Total: 4}, d.Counts)
+ assert.Equal(t, Counts{Rolling: 1, LinedUp: 1, Stalled: 1, PastStand: 1, Total: 4}, d.Counts)
assert.Equal(t, 4, d.Total)
}
@@ 199,7 199,7 @@ func TestBeadsBuildBoardCounts(t *testing.T) {
}
// boardIDs returns every card id on the board, across all lanes.
-func boardIDs(d *BeadsData) []string {
+func boardIDs(d *Data) []string {
var out []string
for i := range d.Lanes {
out = append(out, cardIDs(&d.Lanes[i])...)
M beads/build.go => beads/build.go +22 -22
@@ 14,7 14,7 @@ import (
// Build reads the issue graph and produces either the board or, when ?issue=
// names an issue, that issue's detail pane.
-func Build(ctx context.Context, sess BrowseSession, ref string, query url.Values) (*BeadsData, error) {
+func Build(ctx context.Context, sess BrowseSession, ref string, query url.Values) (*Data, error) {
issues, issuesTotal, err := readRows(ctx, sess, ref, "issues")
if err != nil {
return nil, err
@@ 98,7 98,7 @@ func Build(ctx context.Context, sess BrowseSession, ref string, query url.Values
// Board mode: parse the sticky filters and collect dropdown options from the
// full issue set (options stay stable as filters narrow the board).
- filter := BeadsFilter{
+ filter := Filter{
Query: strings.TrimSpace(query.Get("q")),
Type: query.Get("type"),
Priority: query.Get("priority"),
@@ 109,7 109,7 @@ func Build(ctx context.Context, sess BrowseSession, ref string, query url.Values
opts := collectFilterOptions(issues, issueCols, labelsByIssue)
// Bucket every matching issue into exactly one lane.
- var rolling, linedUp, stalled, pastStand []BeadCard
+ var rolling, linedUp, stalled, pastStand []Card
for _, r := range issues.Rows {
id := cell(issueCols, r, "id")
if !filter.matches(id, r, issueCols, labelsByIssue[id]) {
@@ 125,7 125,7 @@ func Build(ctx context.Context, sess BrowseSession, ref string, query url.Values
if filter.Ready && !ready {
continue
}
- card := BeadCard{
+ card := Card{
ID: id,
Title: cell(issueCols, r, "title"),
Type: cell(issueCols, r, "issue_type"),
@@ 150,13 150,13 @@ func Build(ctx context.Context, sess BrowseSession, ref string, query url.Values
}
created := issueCreatedAt(issues, issueCols)
- for _, lane := range [][]BeadCard{rolling, linedUp, stalled, pastStand} {
+ for _, lane := range [][]Card{rolling, linedUp, stalled, pastStand} {
sortCards(lane, created)
}
- data := &BeadsData{
+ data := &Data{
Mode: "board",
- Lanes: []BeadsLane{
+ Lanes: []Lane{
// Accents are muted Mardi Gras hues (gold / green / violet / gray)
// chosen to read on both the light and dark SourceHut themes. They
// are applied by the template as thin accents (card border, lane
@@ 166,7 166,7 @@ func Build(ctx context.Context, sess BrowseSession, ref string, query url.Values
{Name: "Stalled", Slug: "stalled", Accent: "#9c36b5", Issues: stalled},
{Name: "Past Stand", Slug: "past-stand", Accent: "#868e96", Issues: pastStand},
},
- Counts: BeadsCounts{
+ Counts: Counts{
Rolling: len(rolling),
LinedUp: len(linedUp),
Stalled: len(stalled),
@@ 193,7 193,7 @@ func buildDetail(
deps *browse.RowPage, depCols map[string]int,
labelsByIssue map[string][]string,
catByStatus, catByIssue map[string]string,
-) *BeadsData {
+) *Data {
// id → (title, status, whole row) for edge labels and the subtask rollup.
titleByIssue := map[string]string{}
statusByIssue := map[string]string{}
@@ 209,7 209,7 @@ func buildDetail(
}
}
- data := &BeadsData{Mode: "detail"}
+ data := &Data{Mode: "detail"}
if row == nil {
// Unknown id: a detail pane with a nil Issue; the template shows a
// "not found" note and a link back to the board.
@@ 224,7 224,7 @@ func buildDetail(
status := cell(issueCols, row, "status")
name, accent := laneForCategory(statusCategory(status, catByStatus))
- data.Issue = &BeadIssue{
+ data.Issue = &Issue{
ID: want,
Title: cell(issueCols, row, "title"),
Status: status,
@@ 250,9 250,9 @@ func buildDetail(
Labels: labelsByIssue[want],
}
- edge := func(id, typ string) BeadEdge {
+ edge := func(id, typ string) Edge {
st := statusByIssue[id]
- return BeadEdge{
+ return Edge{
IssueID: id,
Title: titleByIssue[id],
Type: typ,
@@ 274,7 274,7 @@ func buildDetail(
if data.Mode == "epic" && strings.EqualFold(typ, "parent-child") {
cr := rowByID[from]
cat := catByIssue[from]
- st := BeadSubtask{
+ st := Subtask{
ID: from,
Title: titleByIssue[from],
Status: statusByIssue[from],
@@ 333,8 333,8 @@ func buildDetail(
author := cell(ccols, r, "author")
text := cell(ccols, r, "text")
at := cell(ccols, r, "created_at")
- data.Comments = append(data.Comments, BeadComment{Author: author, Text: text, CreatedAt: at})
- data.History = append(data.History, BeadActivity{
+ data.Comments = append(data.Comments, Comment{Author: author, Text: text, CreatedAt: at})
+ data.History = append(data.History, Activity{
Kind: "comment",
Actor: author,
Summary: "commented",
@@ 355,7 355,7 @@ func buildDetail(
et := cell(ecols, r, "event_type")
summary, text := humanizeEvent(et,
cell(ecols, r, "old_value"), cell(ecols, r, "new_value"), cell(ecols, r, "comment"))
- data.History = append(data.History, BeadActivity{
+ data.History = append(data.History, Activity{
Kind: "event",
Event: et,
Actor: cell(ecols, r, "actor"),
@@ 372,7 372,7 @@ func buildDetail(
// collectFilterOptions gathers the distinct issue_type / priority / assignee
// values and label names across all issues, sorted, for the filter dropdowns.
-func collectFilterOptions(issues *browse.RowPage, cols map[string]int, labelsByIssue map[string][]string) BeadsFilterOptions {
+func collectFilterOptions(issues *browse.RowPage, cols map[string]int, labelsByIssue map[string][]string) FilterOptions {
types, prios, assignees, labels := map[string]bool{}, map[string]bool{}, map[string]bool{}, map[string]bool{}
for _, r := range issues.Rows {
if t := cell(cols, r, "issue_type"); t != "" {
@@ 390,7 390,7 @@ func collectFilterOptions(issues *browse.RowPage, cols map[string]int, labelsByI
labels[l] = true
}
}
- return BeadsFilterOptions{
+ return FilterOptions{
Types: sortedKeys(types),
Priorities: sortedKeys(prios), // single digits sort numerically as strings
Assignees: sortedKeys(assignees),
@@ 409,7 409,7 @@ func issueCreatedAt(issues *browse.RowPage, cols map[string]int) map[string]stri
// sortCards orders a lane by priority (0 = highest first), then created_at
// ascending, then id — a stable, deterministic parade order.
-func sortCards(cards []BeadCard, created map[string]string) {
+func sortCards(cards []Card, created map[string]string) {
sort.SliceStable(cards, func(i, j int) bool {
pi, pj := priorityRank(cards[i].Priority), priorityRank(cards[j].Priority)
if pi != pj {
@@ 439,7 439,7 @@ func priorityRank(p string) int {
// sortSubtasks orders an epic's children open-work-first: unclosed before
// closed, then by priority (0 highest), then id — closed subtasks sink to the
// bottom so the actionable ones lead.
-func sortSubtasks(subs []BeadSubtask) {
+func sortSubtasks(subs []Subtask) {
sort.SliceStable(subs, func(i, j int) bool {
ci, cj := subs[i].Category == "closed", subs[j].Category == "closed"
if ci != cj {
@@ 456,7 456,7 @@ func sortSubtasks(subs []BeadSubtask) {
// sortActivity orders the merged history oldest-first (chronological). Timestamps
// share the "YYYY-MM-DD HH:MM:SS" shape across events and comments, so a lexical
// compare is a time compare; ties fall back to id-free but stable order.
-func sortActivity(acts []BeadActivity) {
+func sortActivity(acts []Activity) {
sort.SliceStable(acts, func(i, j int) bool {
return acts[i].CreatedAt < acts[j].CreatedAt
})
M beads/deps.go => beads/deps.go +9 -9
@@ 20,8 20,8 @@ type depLink struct {
// pre-order, flattening the reachable set into indented nodes. Each issue
// appears once (first path wins); depth and node count are bounded so a dense
// or cyclic graph is safe.
-func buildDepTree(root string, adj map[string][]depLink, titleOf, statusOf, catByStatus map[string]string) []BeadTreeNode {
- var out []BeadTreeNode
+func buildDepTree(root string, adj map[string][]depLink, titleOf, statusOf, catByStatus map[string]string) []TreeNode {
+ var out []TreeNode
visited := map[string]bool{root: true}
var dfs func(id string, depth int)
dfs = func(id string, depth int) {
@@ 34,7 34,7 @@ func buildDepTree(root string, adj map[string][]depLink, titleOf, statusOf, catB
}
visited[lnk.to] = true
st := statusOf[lnk.to]
- out = append(out, BeadTreeNode{
+ out = append(out, TreeNode{
ID: lnk.to,
Title: titleOf[lnk.to],
Type: lnk.typ,
@@ 51,7 51,7 @@ func buildDepTree(root string, adj map[string][]depLink, titleOf, statusOf, catB
// hasTransitive reports whether a flattened tree reaches past the direct edges
// (any Depth>0 node) — the signal that it adds something the flat list doesn't.
-func hasTransitive(nodes []BeadTreeNode) bool {
+func hasTransitive(nodes []TreeNode) bool {
for _, n := range nodes {
if n.Depth > 0 {
return true
@@ 66,9 66,9 @@ func hasTransitive(nodes []BeadTreeNode) bool {
// linked under an epic — still appear on the timeline. Returns ok=false when the
// edge does not touch `want` or the row has no timestamp (older schema without
// created_at: skip rather than emit a blank-dated entry).
-func depActivity(want, from, to, typ, at, by string) (BeadActivity, bool) {
+func depActivity(want, from, to, typ, at, by string) (Activity, bool) {
if at == "" || (from != want && to != want) {
- return BeadActivity{}, false
+ return Activity{}, false
}
var summary string
switch strings.ToLower(strings.TrimSpace(typ)) {
@@ 88,15 88,15 @@ func depActivity(want, from, to, typ, at, by string) (BeadActivity, bool) {
// Related is symmetric; emit once (from the issue_id side) to avoid a
// duplicate entry on both endpoints.
if from != want {
- return BeadActivity{}, false
+ return Activity{}, false
}
summary = "linked " + to + " (related)"
default:
if from == want {
summary = "added " + typ + " dependency on " + to
} else {
- return BeadActivity{}, false
+ return Activity{}, false
}
}
- return BeadActivity{Kind: "dep", Event: "dependency", Actor: by, Summary: summary, CreatedAt: at}, true
+ return Activity{Kind: "dep", Event: "dependency", Actor: by, Summary: summary, CreatedAt: at}, true
}
M beads/milestones.go => beads/milestones.go +9 -9
@@ 26,18 26,18 @@ type MilestoneDetail struct {
Done int // closed
InProgress int
Open int // open (or unknown) — the remaining work
- Heads []BeadCard // issue_type == "milestone" — the milestone's own issue(s)
+ Heads []Card // issue_type == "milestone" — the milestone's own issue(s)
Epics []MilestoneEpic // epics in the milestone, each with its nested subtasks
- Loose []BeadCard // members that are neither heads, epics, nor nested subtasks
+ Loose []Card // members that are neither heads, epics, nor nested subtasks
}
// MilestoneEpic is an epic inside a milestone together with the milestone
// members nested under it (parent-child edges pointing at the epic).
type MilestoneEpic struct {
- Card BeadCard
+ Card Card
Done int // closed children, for the "d/t" rollup on the epic row
Total int
- Children []BeadCard
+ Children []Card
}
// Pct is the milestone's completion percentage (0..100) for the progress bar.
@@ 100,12 100,12 @@ func BuildMilestones(ctx context.Context, sess BrowseSession, ref string) (*Mile
issueCols := indexCols(issues.Columns)
byLabel := map[string]*MilestoneDetail{}
- cardsByLabel := map[string][]BeadCard{}
+ cardsByLabel := map[string][]Card{}
unlabeled := 0
for _, r := range issues.Rows {
id := cell(issueCols, r, "id")
cat := statusCategory(cell(issueCols, r, "status"), catByStatus)
- card := BeadCard{
+ card := Card{
ID: id,
Title: cell(issueCols, r, "title"),
Type: cell(issueCols, r, "issue_type"),
@@ 160,7 160,7 @@ func BuildMilestones(ctx context.Context, sess BrowseSession, ref string) (*Mile
// under them, then the leftovers. Only members of this milestone participate —
// membership stays purely label-based; a child nests only when its epic carries
// the same milestone label.
-func (md *MilestoneDetail) arrange(cards []BeadCard, parentsByChild map[string][]string) {
+func (md *MilestoneDetail) arrange(cards []Card, parentsByChild map[string][]string) {
epicByID := map[string]*MilestoneEpic{}
var epics []*MilestoneEpic
for _, c := range cards {
@@ 208,13 208,13 @@ func (md *MilestoneDetail) arrange(cards []BeadCard, parentsByChild map[string][
// sortMilestoneCards orders a milestone's issues open-work-first (closed sinks to
// the bottom), then by priority, then id — a stable, deterministic order.
-func sortMilestoneCards(cards []BeadCard) {
+func sortMilestoneCards(cards []Card) {
sort.SliceStable(cards, func(i, j int) bool {
return milestoneCardLess(cards[i], cards[j])
})
}
-func milestoneCardLess(a, b BeadCard) bool {
+func milestoneCardLess(a, b Card) bool {
ca, cb := a.Category == "closed", b.Category == "closed"
if ca != cb {
return !ca
M beads/model.go => beads/model.go +45 -45
@@ 7,44 7,44 @@ import (
// --- view model --------------------------------------------------------------
-// BeadsData is the opaque .Data value handed to beads.html. Mode discriminates
+// Data is the opaque .Data value handed to beads.html. Mode discriminates
// the renderings: "board" (all lanes), "detail" (one issue), or "epic" (a
// detail whose issue is an epic, which also carries its subtask rollup).
-type BeadsData struct {
+type Data struct {
Mode string // "board" | "detail" | "epic"
// board mode
- Lanes []BeadsLane
- Counts BeadsCounts
- Total int // issues placed on the board (after filtering)
- Truncated bool // an input table exceeded Max and was clipped
- ShownOf int // when Truncated: the reported table total
- Filter BeadsFilter // active board filters (sticky form state)
- FilterOpts BeadsFilterOptions // distinct values for the filter dropdowns
+ Lanes []Lane
+ Counts Counts
+ Total int // issues placed on the board (after filtering)
+ Truncated bool // an input table exceeded Max and was clipped
+ ShownOf int // when Truncated: the reported table total
+ Filter Filter // active board filters (sticky form state)
+ FilterOpts FilterOptions // distinct values for the filter dropdowns
// detail / epic modes
- Issue *BeadIssue
- DependsOn []BeadEdge // this issue depends on … (outgoing, direct)
- DependedOnBy []BeadEdge // … is depended on by this issue (incoming, direct)
- Comments []BeadComment // the comment thread (Comments tab)
- History []BeadActivity // comments + audit events, time-sorted (History tab)
+ Issue *Issue
+ DependsOn []Edge // this issue depends on … (outgoing, direct)
+ DependedOnBy []Edge // … is depended on by this issue (incoming, direct)
+ Comments []Comment // the comment thread (Comments tab)
+ History []Activity // comments + audit events, time-sorted (History tab)
// Transitive dependency trees (flattened, pre-order with Depth), shown only
// when they reach past the direct edges. DependsTree is the full prerequisite
// chain; DependentTree is everything this issue transitively unblocks.
- DependsTree []BeadTreeNode
- DependentTree []BeadTreeNode
+ DependsTree []TreeNode
+ DependentTree []TreeNode
// epic mode: the issue's parent-child children and their rollup.
- Subtasks []BeadSubtask
+ Subtasks []Subtask
SubtaskDone int // # of subtasks in the closed category
SubtaskTotal int // len(Subtasks); the progress denominator
}
-// BeadsFilter holds the active board filters, parsed from the query string and
+// Filter holds the active board filters, parsed from the query string and
// echoed back into the form so selections stick across submits. Empty fields
// mean "no constraint".
-type BeadsFilter struct {
+type Filter struct {
Query string // substring match over id + title (case-insensitive)
Type string // exact issue_type
Priority string // exact priority ("0".."3")
@@ 55,12 55,12 @@ type BeadsFilter struct {
// Active reports whether any filter is set (drives the "Clear" link and the
// empty-board wording).
-func (f BeadsFilter) Active() bool {
+func (f Filter) Active() bool {
return f.Query != "" || f.Type != "" || f.Priority != "" || f.Assignee != "" || f.Label != "" || f.Ready
}
// matches reports whether one issue row passes every set filter.
-func (f BeadsFilter) matches(id string, row []string, cols map[string]int, labels []string) bool {
+func (f Filter) matches(id string, row []string, cols map[string]int, labels []string) bool {
if f.Type != "" && cell(cols, row, "issue_type") != f.Type {
return false
}
@@ 82,26 82,26 @@ func (f BeadsFilter) matches(id string, row []string, cols map[string]int, label
return true
}
-// BeadsFilterOptions lists the distinct values present across all issues, so the
+// FilterOptions lists the distinct values present across all issues, so the
// filter dropdowns offer only real choices. Collected from the unfiltered set so
// the options don't shrink as a filter narrows the board.
-type BeadsFilterOptions struct {
+type FilterOptions struct {
Types []string
Priorities []string // "0".."3"
Assignees []string
Labels []string
}
-// BeadsLane is one parade lane and the cards in it.
-type BeadsLane struct {
+// Lane is one parade lane and the cards in it.
+type Lane struct {
Name string // human label, e.g. "Rolling"
Slug string // css-safe identifier, e.g. "rolling"
Accent string // hex accent color for the lane header/border
- Issues []BeadCard
+ Issues []Card
}
-// BeadsCounts is the marquee: per-lane totals plus the grand total.
-type BeadsCounts struct {
+// Counts is the marquee: per-lane totals plus the grand total.
+type Counts struct {
Rolling int
LinedUp int
Stalled int
@@ 109,8 109,8 @@ type BeadsCounts struct {
Total int
}
-// BeadCard is one issue as it appears on the board.
-type BeadCard struct {
+// Card is one issue as it appears on the board.
+type Card struct {
ID string
Title string
Type string
@@ 125,7 125,7 @@ type BeadCard struct {
// PriorityLabel renders the numeric priority as a P-pill label ("P0".."P3"),
// or "" when unset/unparseable so the template can omit the marker.
-func (c BeadCard) PriorityLabel() string {
+func (c Card) PriorityLabel() string {
if c.Priority == "" {
return ""
}
@@ 135,8 135,8 @@ func (c BeadCard) PriorityLabel() string {
return "P" + c.Priority
}
-// BeadEdge is one dependency edge to another issue, linked in the detail pane.
-type BeadEdge struct {
+// Edge is one dependency edge to another issue, linked in the detail pane.
+type Edge struct {
IssueID string
Title string
Type string
@@ 144,10 144,10 @@ type BeadEdge struct {
Closed bool
}
-// BeadTreeNode is one node in a flattened transitive dependency tree. Depth is
+// TreeNode is one node in a flattened transitive dependency tree. Depth is
// the indentation level (0 = a direct edge of the root issue); Type is the
// dependency type of the edge that reached this node.
-type BeadTreeNode struct {
+type TreeNode struct {
ID string
Title string
Type string
@@ 156,18 156,18 @@ type BeadTreeNode struct {
Depth int
}
-// BeadComment is one row of the comments thread.
-type BeadComment struct {
+// Comment is one row of the comments thread.
+type Comment struct {
Author string
Text string
CreatedAt string
}
-// BeadActivity is one entry in the merged history timeline: either a comment or
+// Activity is one entry in the merged history timeline: either a comment or
// an audit event from the events table. Summary is a human-readable one-liner
// ("changed status to in_progress"); Text carries the comment body or an event's
// free-text note. Kind drives the icon/label in the template.
-type BeadActivity struct {
+type Activity struct {
Kind string // "comment" | "event"
Event string // events only: the event_type (created/status_changed/updated/closed/…)
Actor string
@@ 176,10 176,10 @@ type BeadActivity struct {
CreatedAt string
}
-// BeadSubtask is one child of an epic — the "from" side of a parent-child
+// Subtask is one child of an epic — the "from" side of a parent-child
// dependency that points at the epic. Category (open/in_progress/closed) drives
// the status accent and feeds the epic's progress rollup.
-type BeadSubtask struct {
+type Subtask struct {
ID string
Title string
Status string
@@ 191,7 191,7 @@ type BeadSubtask struct {
// PriorityLabel renders a subtask's numeric priority as a P-pill ("P0".."P3"),
// or "" when unset/unparseable.
-func (s BeadSubtask) PriorityLabel() string {
+func (s Subtask) PriorityLabel() string {
if s.Priority == "" {
return ""
}
@@ 203,18 203,18 @@ func (s BeadSubtask) PriorityLabel() string {
// SubtaskPct is the epic's completion percentage (0..100), for the progress bar
// width. Zero subtasks reads as 0%.
-func (d *BeadsData) SubtaskPct() int {
+func (d *Data) SubtaskPct() int {
if d.SubtaskTotal == 0 {
return 0
}
return d.SubtaskDone * 100 / d.SubtaskTotal
}
-// BeadIssue is the full issue shown in the detail pane. The field set mirrors
+// Issue is the full issue shown in the detail pane. The field set mirrors
// the user-facing columns bd surfaces for an issue (see `bd show`): identity and
// status, the four long-text bodies, effort/reference metadata, the full
// timestamp trail, and the close reason recorded when an issue is resolved.
-type BeadIssue struct {
+type Issue struct {
ID string
Title string
Status string
M web/beads.go => web/beads.go +1 -1
@@ 30,7 30,7 @@ func (*beadsView) Template() string { return "beads.html" }
func (*beadsView) Applies(tables []browse.TableInfo) bool { return beads.Applies(tables) }
// Build reads the issue graph and produces either the board or, when ?issue=
-// names an issue, that issue's detail pane. The result is a *beads.BeadsData,
+// names an issue, that issue's detail pane. The result is a *beads.Data,
// handed to beads.html as its .Data.
func (*beadsView) Build(ctx context.Context, sess BrowseSession, _ *core.Repo, ref string, query url.Values) (any, error) {
data, err := beads.Build(ctx, sess, ref, query)
M web/realdata_test.go => web/realdata_test.go +3 -3
@@ 63,13 63,13 @@ func TestRealBeadsStore(t *testing.T) {
if err != nil {
t.Fatalf("board build: %v", err)
}
- bd := board.(*beads.BeadsData)
+ bd := board.(*beads.Data)
t.Logf("board total=%d types=%v priorities=%v labels=%v",
bd.Total, bd.FilterOpts.Types, bd.FilterOpts.Priorities, bd.FilterOpts.Labels)
if len(bd.FilterOpts.Types) > 0 {
ft := bd.FilterOpts.Types[0]
filtered, _ := v.Build(ctx, dbh, nil, ref, url.Values{"type": {ft}})
- t.Logf("filter type=%q → %d issues (of %d)", ft, filtered.(*beads.BeadsData).Total, bd.Total)
+ t.Logf("filter type=%q → %d issues (of %d)", ft, filtered.(*beads.Data).Total, bd.Total)
}
readyN := 0
for i := range bd.Lanes {
@@ 106,7 106,7 @@ func TestRealBeadsStore(t *testing.T) {
if err != nil {
t.Fatalf("build: %v", err)
}
- data := raw.(*beads.BeadsData)
+ data := raw.(*beads.Data)
if data.Issue == nil {
t.Fatalf("issue %q not found", want)
}