diff --git a/cmd/bd/list.go b/cmd/bd/list.go index 7b6b1a9c..5011f52e 100644 --- a/cmd/bd/list.go +++ b/cmd/bd/list.go @@ -38,9 +38,9 @@ func parseTimeFlag(s string) (time.Time, error) { return time.Time{}, fmt.Errorf("unable to parse time %q (try formats: 2006-01-02, 2006-01-02T15:04:05, or RFC3339)", s) } -// pinIndicator returns a pushpin emoji prefix for pinned issues (bd-18b) +// pinIndicator returns a pushpin emoji prefix for pinned issues (bd-18b, bd-7h5) func pinIndicator(issue *types.Issue) string { - if issue.Status == types.StatusPinned { + if issue.Pinned { return "📌 " } return "" diff --git a/internal/importer/importer.go b/internal/importer/importer.go index 88db09b3..5d21c75f 100644 --- a/internal/importer/importer.go +++ b/internal/importer/importer.go @@ -554,6 +554,8 @@ func upsertIssues(ctx context.Context, sqliteStore *sqlite.SQLiteStorage, issues updates["acceptance_criteria"] = incoming.AcceptanceCriteria updates["notes"] = incoming.Notes updates["closed_at"] = incoming.ClosedAt + // Pinned field (bd-7h5) + updates["pinned"] = incoming.Pinned if incoming.Assignee != "" { updates["assignee"] = incoming.Assignee @@ -647,6 +649,8 @@ func upsertIssues(ctx context.Context, sqliteStore *sqlite.SQLiteStorage, issues updates["acceptance_criteria"] = incoming.AcceptanceCriteria updates["notes"] = incoming.Notes updates["closed_at"] = incoming.ClosedAt + // Pinned field (bd-7h5) + updates["pinned"] = incoming.Pinned if incoming.Assignee != "" { updates["assignee"] = incoming.Assignee diff --git a/internal/importer/utils.go b/internal/importer/utils.go index b6828730..0cf5c6ab 100644 --- a/internal/importer/utils.go +++ b/internal/importer/utils.go @@ -112,6 +112,15 @@ func (fc *fieldComparator) equalPriority(existing int, newVal interface{}) bool return ok && int64(existing) == newPriority } +func (fc *fieldComparator) equalBool(existingVal bool, newVal interface{}) bool { + switch t := newVal.(type) { + case bool: + return existingVal == t + default: + return false + } +} + func (fc *fieldComparator) checkFieldChanged(key string, existing *types.Issue, newVal interface{}) bool { switch key { case "title": @@ -134,6 +143,8 @@ func (fc *fieldComparator) checkFieldChanged(key string, existing *types.Issue, return !fc.equalStr(existing.Assignee, newVal) case "external_ref": return !fc.equalPtrStr(existing.ExternalRef, newVal) + case "pinned": + return !fc.equalBool(existing.Pinned, newVal) default: return false } diff --git a/internal/storage/sqlite/events.go b/internal/storage/sqlite/events.go index 1bdd11e8..3e24b692 100644 --- a/internal/storage/sqlite/events.go +++ b/internal/storage/sqlite/events.go @@ -121,7 +121,7 @@ func (s *SQLiteStorage) GetStatistics(ctx context.Context) (*types.Statistics, e COALESCE(SUM(CASE WHEN status = 'closed' THEN 1 ELSE 0 END), 0) as closed, COALESCE(SUM(CASE WHEN status = 'deferred' THEN 1 ELSE 0 END), 0) as deferred, COALESCE(SUM(CASE WHEN status = 'tombstone' THEN 1 ELSE 0 END), 0) as tombstone, - COALESCE(SUM(CASE WHEN status = 'pinned' THEN 1 ELSE 0 END), 0) as pinned + COALESCE(SUM(CASE WHEN pinned = 1 THEN 1 ELSE 0 END), 0) as pinned FROM issues `).Scan(&stats.TotalIssues, &stats.OpenIssues, &stats.InProgressIssues, &stats.ClosedIssues, &stats.DeferredIssues, &stats.TombstoneIssues, &stats.PinnedIssues) if err != nil { diff --git a/internal/storage/sqlite/queries.go b/internal/storage/sqlite/queries.go index 13bca543..b9c90d2d 100644 --- a/internal/storage/sqlite/queries.go +++ b/internal/storage/sqlite/queries.go @@ -558,6 +558,8 @@ var allowedUpdateFields = map[string]bool{ // Messaging fields (bd-kwro) "sender": true, "ephemeral": true, + // Pinned field (bd-7h5) + "pinned": true, // NOTE: replies_to, relates_to, duplicate_of, superseded_by removed per Decision 004 // Use AddDependency() to create graph edges instead }