Files
multica/server/pkg/protocol/events.go
Naiyuan Qing c462c80b70 feat(issues): server-side sorting + float8 fractional positions (MUL-2314)
Replaces the half-functional `position = 0` write-default + client-side
`sortIssues` re-sort with a server-authoritative sort_by/sort_direction matrix
and float8 fractional indexing for drag-drop:

- Server: ListIssues / ListGroupedIssues accept sort_by (position, priority,
  start_date, due_date, created_at, updated_at, title) and sort_direction;
  unknown values silently downgrade to created_at desc per the API
  compatibility contract.
- Server: new issues allocate MIN(position) - 1.0 inside the existing
  IncrementIssueCounter transaction so the legacy `position ASC` ordering
  still places new issues at the top for old desktop clients.
- Server: migration 093 backfills existing rows with sparse float positions
  (newest first) and adds a (workspace_id, status, position) index.
- Server: new PositionRebalanceService re-spaces a bucket asynchronously once
  the neighbour gap drops below 1e-9, then publishes the new
  `issue:rebalanced` WS event.
- Frontend: issueKeys.list / myList carry a sort tuple in their key, so every
  list-cache reader/writer migrated from exact `setQueryData` to prefix
  `setQueriesData` / `getQueriesData` (mutations, ws-updaters, delete-cache,
  mention-suggestion, issue-chip, issue-detail). Snapshots and rollbacks
  iterate per-variant so every mounted sort variant rolls back cleanly.
- Frontend: `packages/views/issues/utils/sort.ts` deleted; list-view /
  board-view trust server order, and drag-drop is guarded to Manual sort
  with a toast pointing the user back to Manual.
- Frontend: `issue:rebalanced` parses through `parseWithFallback` with a zod
  schema + EMPTY_ISSUE_REBALANCED_PAYLOAD fallback; both the strict and the
  fallback paths invalidate `list` / `myAll` / `assigneeGroupsAll` /
  `myAssigneeGroupsAll` prefixes so server-authoritative refetch fixes order.
- Tests: Go regression for unknown sort_by fallback and `MIN(position)-1`
  ordering; new core schema test covers the four failure modes for the
  rebalance payload. Existing 575+ FE tests and full Go suite pass.

Phase 1+2 are bundled per Leader's release directive — splitting would leave
a window where new issues regress to "appears at the bottom" for old clients.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-18 13:36:47 +08:00

125 lines
4.3 KiB
Go

package protocol
// Event types for WebSocket communication between server, web clients, and daemon.
const (
// Issue events
EventIssueCreated = "issue:created"
EventIssueUpdated = "issue:updated"
EventIssueDeleted = "issue:deleted"
EventIssueRebalanced = "issue:rebalanced"
// Comment events
EventCommentCreated = "comment:created"
EventCommentUpdated = "comment:updated"
EventCommentDeleted = "comment:deleted"
EventCommentResolved = "comment:resolved"
EventCommentUnresolved = "comment:unresolved"
EventReactionAdded = "reaction:added"
EventReactionRemoved = "reaction:removed"
EventIssueReactionAdded = "issue_reaction:added"
EventIssueReactionRemoved = "issue_reaction:removed"
// Agent events
EventAgentStatus = "agent:status"
EventAgentCreated = "agent:created"
EventAgentArchived = "agent:archived"
EventAgentRestored = "agent:restored"
// Task events (server <-> daemon).
// Each event maps to a status transition on agent_task_queue. Front-end
// subscribes by `task:` prefix and invalidates the workspace task
// snapshot, so the granularity here is "what does the user want to see
// change" — not "every internal status flip".
EventTaskQueued = "task:queued" // ∅ → queued (enqueue / retry create)
EventTaskDispatch = "task:dispatch" // queued → dispatched (daemon claim)
EventTaskProgress = "task:progress"
EventTaskCompleted = "task:completed" // running → completed
EventTaskFailed = "task:failed" // running → failed
EventTaskMessage = "task:message"
EventTaskCancelled = "task:cancelled" // * → cancelled
// Inbox events
EventInboxNew = "inbox:new"
EventInboxRead = "inbox:read"
EventInboxArchived = "inbox:archived"
EventInboxBatchRead = "inbox:batch-read"
EventInboxBatchArchived = "inbox:batch-archived"
// Workspace events
EventWorkspaceUpdated = "workspace:updated"
EventWorkspaceDeleted = "workspace:deleted"
// Member events
EventMemberAdded = "member:added"
EventMemberUpdated = "member:updated"
EventMemberRemoved = "member:removed"
// Subscriber events
EventSubscriberAdded = "subscriber:added"
EventSubscriberRemoved = "subscriber:removed"
// Activity events
EventActivityCreated = "activity:created"
// Skill events
EventSkillCreated = "skill:created"
EventSkillUpdated = "skill:updated"
EventSkillDeleted = "skill:deleted"
// Chat events
EventChatMessage = "chat:message"
EventChatDone = "chat:done"
EventChatSessionRead = "chat:session_read"
EventChatSessionDeleted = "chat:session_deleted"
EventChatSessionUpdated = "chat:session_updated"
// Project events
EventProjectCreated = "project:created"
EventProjectUpdated = "project:updated"
EventProjectDeleted = "project:deleted"
EventProjectResourceCreated = "project_resource:created"
EventProjectResourceDeleted = "project_resource:deleted"
// Label events
EventLabelCreated = "label:created"
EventLabelUpdated = "label:updated"
EventLabelDeleted = "label:deleted"
EventIssueLabelsChanged = "issue_labels:changed"
// Pin events
EventPinCreated = "pin:created"
EventPinDeleted = "pin:deleted"
EventPinReordered = "pin:reordered"
// Invitation events
EventInvitationCreated = "invitation:created"
EventInvitationAccepted = "invitation:accepted"
EventInvitationDeclined = "invitation:declined"
EventInvitationRevoked = "invitation:revoked"
// Autopilot events
EventAutopilotCreated = "autopilot:created"
EventAutopilotUpdated = "autopilot:updated"
EventAutopilotDeleted = "autopilot:deleted"
EventAutopilotRunStart = "autopilot:run_start"
EventAutopilotRunDone = "autopilot:run_done"
// Squad events
EventSquadCreated = "squad:created"
EventSquadUpdated = "squad:updated"
EventSquadDeleted = "squad:deleted"
// Daemon events
EventDaemonHeartbeat = "daemon:heartbeat"
EventDaemonHeartbeatAck = "daemon:heartbeat_ack"
EventDaemonRegister = "daemon:register"
EventDaemonTaskAvailable = "daemon:task_available"
// GitHub integration events
EventGitHubInstallationCreated = "github_installation:created"
EventGitHubInstallationDeleted = "github_installation:deleted"
EventPullRequestLinked = "pull_request:linked"
EventPullRequestUpdated = "pull_request:updated"
EventPullRequestUnlinked = "pull_request:unlinked"
)