feat(rpc): enrich MutationEvent with Title and Assignee fields
Add Title and Assignee fields to MutationEvent struct so activity feeds can display meaningful context without extra lookups. Updated emitMutation signature to accept these values and modified all callers: - Create: passes issue.Title and issue.Assignee directly - Update/Close: moved emitMutation after GetIssue to access enriched data - Delete: uses existing issue lookup before deletion - Dep/Label/Comment ops: passes empty strings (would require extra lookup) Fixes bd-gqxd 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -80,6 +80,8 @@ const (
|
||||
type MutationEvent struct {
|
||||
Type string // One of the Mutation* constants
|
||||
IssueID string // e.g., "bd-42"
|
||||
Title string // Issue title for display context (may be empty for some operations)
|
||||
Assignee string // Issue assignee for display context (may be empty)
|
||||
Timestamp time.Time
|
||||
// Optional metadata for richer events (used by status, bonded, etc.)
|
||||
OldStatus string `json:"old_status,omitempty"` // Previous status (for status events)
|
||||
@@ -138,10 +140,13 @@ func NewServer(socketPath string, store storage.Storage, workspacePath string, d
|
||||
// emitMutation sends a mutation event to the daemon's event-driven loop.
|
||||
// Non-blocking: drops event if channel is full (sync will happen eventually).
|
||||
// Also stores in recent mutations buffer for polling.
|
||||
func (s *Server) emitMutation(eventType, issueID string) {
|
||||
// Title and assignee provide context for activity feeds; pass empty strings if unknown.
|
||||
func (s *Server) emitMutation(eventType, issueID, title, assignee string) {
|
||||
s.emitRichMutation(MutationEvent{
|
||||
Type: eventType,
|
||||
IssueID: issueID,
|
||||
Type: eventType,
|
||||
IssueID: issueID,
|
||||
Title: title,
|
||||
Assignee: assignee,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -350,7 +350,7 @@ func (s *Server) handleCreate(req *Request) Response {
|
||||
}
|
||||
|
||||
// Emit mutation event for event-driven daemon
|
||||
s.emitMutation(MutationCreate, issue.ID)
|
||||
s.emitMutation(MutationCreate, issue.ID, issue.Title, issue.Assignee)
|
||||
|
||||
data, _ := json.Marshal(issue)
|
||||
return Response{
|
||||
@@ -470,11 +470,13 @@ func (s *Server) handleUpdate(req *Request) Response {
|
||||
s.emitRichMutation(MutationEvent{
|
||||
Type: MutationStatus,
|
||||
IssueID: updateArgs.ID,
|
||||
Title: issue.Title,
|
||||
Assignee: issue.Assignee,
|
||||
OldStatus: string(issue.Status),
|
||||
NewStatus: *updateArgs.Status,
|
||||
})
|
||||
} else {
|
||||
s.emitMutation(MutationUpdate, updateArgs.ID)
|
||||
s.emitMutation(MutationUpdate, updateArgs.ID, issue.Title, issue.Assignee)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,6 +546,8 @@ func (s *Server) handleClose(req *Request) Response {
|
||||
s.emitRichMutation(MutationEvent{
|
||||
Type: MutationStatus,
|
||||
IssueID: closeArgs.ID,
|
||||
Title: issue.Title,
|
||||
Assignee: issue.Assignee,
|
||||
OldStatus: oldStatus,
|
||||
NewStatus: "closed",
|
||||
})
|
||||
@@ -640,7 +644,7 @@ func (s *Server) handleDelete(req *Request) Response {
|
||||
}
|
||||
|
||||
// Emit mutation event for event-driven daemon
|
||||
s.emitMutation(MutationDelete, issueID)
|
||||
s.emitMutation(MutationDelete, issueID, issue.Title, issue.Assignee)
|
||||
deletedCount++
|
||||
}
|
||||
|
||||
@@ -1421,7 +1425,7 @@ func (s *Server) handleGateCreate(req *Request) Response {
|
||||
}
|
||||
|
||||
// Emit mutation event
|
||||
s.emitMutation(MutationCreate, gate.ID)
|
||||
s.emitMutation(MutationCreate, gate.ID, gate.Title, gate.Assignee)
|
||||
|
||||
data, _ := json.Marshal(GateCreateResult{ID: gate.ID})
|
||||
return Response{
|
||||
@@ -1702,7 +1706,7 @@ func (s *Server) handleGateWait(req *Request) Response {
|
||||
}
|
||||
|
||||
// Emit mutation event
|
||||
s.emitMutation(MutationUpdate, gateID)
|
||||
s.emitMutation(MutationUpdate, gateID, gate.Title, gate.Assignee)
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(GateWaitResult{AddedCount: addedCount})
|
||||
|
||||
@@ -41,7 +41,8 @@ func (s *Server) handleDepAdd(req *Request) Response {
|
||||
}
|
||||
|
||||
// Emit mutation event for event-driven daemon
|
||||
s.emitMutation(MutationUpdate, depArgs.FromID)
|
||||
// Title/assignee empty for dependency operations (would require extra lookup)
|
||||
s.emitMutation(MutationUpdate, depArgs.FromID, "", "")
|
||||
|
||||
return Response{Success: true}
|
||||
}
|
||||
@@ -73,7 +74,8 @@ func (s *Server) handleSimpleStoreOp(req *Request, argsPtr interface{}, argDesc
|
||||
}
|
||||
|
||||
// Emit mutation event for event-driven daemon
|
||||
s.emitMutation(MutationUpdate, issueID)
|
||||
// Title/assignee empty for simple store operations (would require extra lookup)
|
||||
s.emitMutation(MutationUpdate, issueID, "", "")
|
||||
|
||||
return Response{Success: true}
|
||||
}
|
||||
@@ -147,7 +149,8 @@ func (s *Server) handleCommentAdd(req *Request) Response {
|
||||
}
|
||||
|
||||
// Emit mutation event for event-driven daemon
|
||||
s.emitMutation(MutationComment, commentArgs.ID)
|
||||
// Title/assignee empty for comment operations (would require extra lookup)
|
||||
s.emitMutation(MutationComment, commentArgs.ID, "", "")
|
||||
|
||||
data, _ := json.Marshal(comment)
|
||||
return Response{
|
||||
|
||||
@@ -13,7 +13,7 @@ func TestEmitMutation(t *testing.T) {
|
||||
server := NewServer("/tmp/test.sock", store, "/tmp", "/tmp/test.db")
|
||||
|
||||
// Emit a mutation
|
||||
server.emitMutation(MutationCreate, "bd-123")
|
||||
server.emitMutation(MutationCreate, "bd-123", "Test Issue", "")
|
||||
|
||||
// Check that mutation was stored in buffer
|
||||
mutations := server.GetRecentMutations(0)
|
||||
@@ -45,14 +45,14 @@ func TestGetRecentMutations_TimestampFiltering(t *testing.T) {
|
||||
server := NewServer("/tmp/test.sock", store, "/tmp", "/tmp/test.db")
|
||||
|
||||
// Emit mutations with delays
|
||||
server.emitMutation(MutationCreate, "bd-1")
|
||||
server.emitMutation(MutationCreate, "bd-1", "Issue 1", "")
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
checkpoint := time.Now().UnixMilli()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
server.emitMutation(MutationUpdate, "bd-2")
|
||||
server.emitMutation(MutationUpdate, "bd-3")
|
||||
server.emitMutation(MutationUpdate, "bd-2", "Issue 2", "")
|
||||
server.emitMutation(MutationUpdate, "bd-3", "Issue 3", "")
|
||||
|
||||
// Get mutations after checkpoint
|
||||
mutations := server.GetRecentMutations(checkpoint)
|
||||
@@ -82,7 +82,7 @@ func TestGetRecentMutations_CircularBuffer(t *testing.T) {
|
||||
|
||||
// Emit more than maxMutationBuffer (100) mutations
|
||||
for i := 0; i < 150; i++ {
|
||||
server.emitMutation(MutationCreate, "bd-"+string(rune(i)))
|
||||
server.emitMutation(MutationCreate, "bd-"+string(rune(i)), "", "")
|
||||
time.Sleep(time.Millisecond) // Ensure different timestamps
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ func TestGetRecentMutations_ConcurrentAccess(t *testing.T) {
|
||||
// Writer goroutine
|
||||
go func() {
|
||||
for i := 0; i < 50; i++ {
|
||||
server.emitMutation(MutationUpdate, "bd-write")
|
||||
server.emitMutation(MutationUpdate, "bd-write", "", "")
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
done <- true
|
||||
@@ -141,11 +141,11 @@ func TestHandleGetMutations(t *testing.T) {
|
||||
server := NewServer("/tmp/test.sock", store, "/tmp", "/tmp/test.db")
|
||||
|
||||
// Emit some mutations
|
||||
server.emitMutation(MutationCreate, "bd-1")
|
||||
server.emitMutation(MutationCreate, "bd-1", "Issue 1", "")
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
checkpoint := time.Now().UnixMilli()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
server.emitMutation(MutationUpdate, "bd-2")
|
||||
server.emitMutation(MutationUpdate, "bd-2", "Issue 2", "")
|
||||
|
||||
// Create RPC request
|
||||
args := GetMutationsArgs{Since: checkpoint}
|
||||
@@ -213,7 +213,7 @@ func TestMutationEventTypes(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, mutationType := range types {
|
||||
server.emitMutation(mutationType, "bd-test")
|
||||
server.emitMutation(mutationType, "bd-test", "", "")
|
||||
}
|
||||
|
||||
mutations := server.GetRecentMutations(0)
|
||||
@@ -305,7 +305,7 @@ func TestMutationTimestamps(t *testing.T) {
|
||||
server := NewServer("/tmp/test.sock", store, "/tmp", "/tmp/test.db")
|
||||
|
||||
before := time.Now()
|
||||
server.emitMutation(MutationCreate, "bd-123")
|
||||
server.emitMutation(MutationCreate, "bd-123", "Test Issue", "")
|
||||
after := time.Now()
|
||||
|
||||
mutations := server.GetRecentMutations(0)
|
||||
@@ -327,7 +327,7 @@ func TestEmitMutation_NonBlocking(t *testing.T) {
|
||||
// Fill the buffer (default size is 512 from BEADS_MUTATION_BUFFER or default)
|
||||
for i := 0; i < 600; i++ {
|
||||
// This should not block even when channel is full
|
||||
server.emitMutation(MutationCreate, "bd-test")
|
||||
server.emitMutation(MutationCreate, "bd-test", "", "")
|
||||
}
|
||||
|
||||
// Verify mutations were still stored in recent buffer
|
||||
|
||||
Reference in New Issue
Block a user