server

package
v1.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 9, 2026 License: MIT Imports: 30 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ConfigureLogging

func ConfigureLogging()

ConfigureLogging sets the log package to use RFC3339 UTC timestamps, matching the format used by RequestLogger.

func GetStaticHandler

func GetStaticHandler() http.Handler

GetStaticHandler returns an http.Handler for the embedded static files.

func RequestLogger

func RequestLogger(next http.Handler) http.Handler

RequestLogger wraps an http.Handler and logs each request as a comma-separated line:

timestamp,method,path,status,duration_ms,client

Types

type AgeBuckets added in v1.2.0

type AgeBuckets struct {
	Under1d  int `json:"under_1d"`
	Under3d  int `json:"under_3d"`
	Under7d  int `json:"under_7d"`
	Under14d int `json:"under_14d"`
	Under30d int `json:"under_30d"`
	Over30d  int `json:"over_30d"`
}

type ArtifactResponse

type ArtifactResponse struct {
	ArtifactType string    `json:"artifact_type"`
	Filename     string    `json:"filename"`
	UpdatedAt    time.Time `json:"updated_at"`
	UpdatedBy    string    `json:"updated_by"`
}

type AttentionItem added in v1.2.0

type AttentionItem struct {
	IssueID  string        `json:"issue_id"`
	Title    string        `json:"title"`
	Kind     AttentionKind `json:"kind"`
	AgeDays  int           `json:"age_days,omitempty"`
	Priority int           `json:"priority,omitempty"`
}

type AttentionKind added in v1.2.0

type AttentionKind string

AttentionKind classifies why an issue is surfaced in the Attention card.

const (
	AttentionBlocker      AttentionKind = "blocker"
	AttentionStaleWIP     AttentionKind = "stale_wip"
	AttentionHighPriority AttentionKind = "high_priority"
)

type BugAgeBuckets added in v1.2.0

type BugAgeBuckets struct {
	Under24h int `json:"under_24h"`
	Under48h int `json:"under_48h"`
	Under5d  int `json:"under_5d"`
	Under14d int `json:"under_14d"`
	Under1mo int `json:"under_1mo"`
	Over1mo  int `json:"over_1mo"`
}

type CommentResponse

type CommentResponse struct {
	ID        string    `json:"id"`
	Text      string    `json:"text"`
	CreatedBy string    `json:"created_by"`
	CreatedAt time.Time `json:"created_at"`
}

type DailyBucket added in v1.2.0

type DailyBucket struct {
	Date   string `json:"date"` // YYYY-MM-DD
	Points int    `json:"points"`
}

type DependencyResponse

type DependencyResponse struct {
	SourceID string `json:"source_id"`
	TargetID string `json:"target_id"`
	Kind     string `json:"kind"`
}

type EpicProgress added in v1.2.0

type EpicProgress struct {
	IssueID         string `json:"issue_id"`
	Title           string `json:"title"`
	ChildrenDone    int    `json:"children_done"`
	ChildrenTotal   int    `json:"children_total"`
	PointsDone      int    `json:"points_done"`
	PointsTotal     int    `json:"points_total"`
	PointsRemaining int    `json:"points_remaining"`
	Stale           bool   `json:"stale"`
}

func ComputeEpicProgress added in v1.2.0

func ComputeEpicProgress(issues map[string]*model.Issue, now time.Time) []EpicProgress

ComputeEpicProgress returns one row per active epic. An "epic" is an issue labeled "epic" (case-insensitive), with at least one child, in an active status (PLANNED / DOING / BLOCKED). BACKLOG epics are queued, not active, and DONE epics are completed — both excluded. Sorted by remaining points desc — biggest active initiative first.

type IssueResponse

type IssueResponse struct {
	ID               string               `json:"id"`
	Title            string               `json:"title"`
	Description      string               `json:"description"`
	Status           string               `json:"status"`
	IsInferred       bool                 `json:"is_inferred,omitempty"`
	ParentID         string               `json:"parent_id,omitempty"`
	Estimate         int                  `json:"estimate"`
	Priority         int                  `json:"priority"`
	SortOrder        string               `json:"sort_order"`
	Assignee         string               `json:"assignee,omitempty"`
	CycleID          string               `json:"cycle_id,omitempty"`
	EffectiveCycleID string               `json:"effective_cycle_id,omitempty"`
	BranchStats      *model.BranchStats   `json:"branch_stats,omitempty"`
	Labels           []string             `json:"labels,omitempty"`
	Dependencies     []DependencyResponse `json:"dependencies,omitempty"`
	Artifacts        []ArtifactResponse   `json:"artifacts,omitempty"`
	Comments         []CommentResponse    `json:"comments,omitempty"`
	CreatedAt        time.Time            `json:"created_at"`
	CreatedBy        string               `json:"created_by"`
	UpdatedAt        time.Time            `json:"updated_at"`
	IsPending        bool                 `json:"is_pending"`
}

type PendingEventEntry

type PendingEventEntry struct {
	IssueID   string      `json:"issue_id"`
	Type      string      `json:"type"`
	Payload   interface{} `json:"payload,omitempty"`
	CreatedAt time.Time   `json:"created_at"`
}

type PendingResponse

type PendingResponse struct {
	HasPending bool                `json:"has_pending"`
	Events     []PendingEventEntry `json:"events"`
	IssueIDs   []string            `json:"issue_ids"`
}

type PulseMetrics added in v1.2.0

type PulseMetrics struct {
	Velocity struct {
		CurrentWeekPoints int              `json:"current_week_points"`
		Last7dPoints      int              `json:"last_7d_points"`
		Prior7dPoints     int              `json:"prior_7d_points"`
		Delta             int              `json:"delta"`
		WeeklyBuckets     []VelocityBucket `json:"weekly_buckets"`
		DailyBuckets      []DailyBucket    `json:"daily_buckets"`
	} `json:"velocity"`
	Throughput struct {
		Last7d  int `json:"last_7d"`
		Prior7d int `json:"prior_7d"`
		Delta   int `json:"delta"` // signed absolute count delta
	} `json:"throughput"`
	WIP struct {
		Total              int `json:"total"`
		Stale              int `json:"stale"`
		StaleThresholdDays int `json:"stale_threshold_days"`
	} `json:"wip"`
	Blockers struct {
		Total      int `json:"total"`
		OldestDays int `json:"oldest_days"`
	} `json:"blockers"`
	Flow struct {
		CycleTimeHrs    float64    `json:"cycle_time_hrs"`
		CycleTimeP75Hrs float64    `json:"cycle_time_p75_hrs"`
		CycleTimeP90Hrs float64    `json:"cycle_time_p90_hrs"`
		CycleTimeMinHrs float64    `json:"cycle_time_min_hrs"`
		CycleTimeMaxHrs float64    `json:"cycle_time_max_hrs"`
		CycleCount      int        `json:"cycle_count"`
		LeadTimeHrs     float64    `json:"lead_time_hrs"`
		LeadTimeP75Hrs  float64    `json:"lead_time_p75_hrs"`
		LeadTimeP90Hrs  float64    `json:"lead_time_p90_hrs"`
		LeadTimeMinHrs  float64    `json:"lead_time_min_hrs"`
		LeadTimeMaxHrs  float64    `json:"lead_time_max_hrs"`
		LeadCount       int        `json:"lead_count"`
		Staleness       AgeBuckets `json:"staleness"`
		StalenessTotal  int        `json:"staleness_total"`
	} `json:"flow"`
	Attention []AttentionItem `json:"attention"`
	Workload  []WorkloadEntry `json:"workload"`
	Epics     []EpicProgress  `json:"epics"`
	Trends    TrendsBlock     `json:"trends"`
}

func ComputePulseMetrics added in v1.2.0

func ComputePulseMetrics(issues map[string]*model.Issue, now time.Time) PulseMetrics

type SSEEvent

type SSEEvent struct {
	Type    string `json:"type"`
	IssueID string `json:"issue_id"`
}

SSEEvent is a lightweight notification sent to connected clients.

type SSEHub

type SSEHub struct {
	// contains filtered or unexported fields
}

SSEHub manages connected SSE clients and broadcasts events.

func NewSSEHub

func NewSSEHub() *SSEHub

NewSSEHub creates a new hub.

func (*SSEHub) Broadcast

func (h *SSEHub) Broadcast(evt SSEEvent)

Broadcast sends an event to all connected clients.

func (*SSEHub) ClientCount

func (h *SSEHub) ClientCount() int

ClientCount returns the number of connected clients.

type Server

type Server struct {
	Config   *config.Config
	Port     int
	DevMode  bool
	DevPort  int
	Headless bool

	// Auth fields — nil when auth is disabled (local board mode).
	NonceStore     *auth.NonceStore
	AuthorizedKeys *auth.AuthorizedKeys
	SigningKey     ed25519.PrivateKey
	VerifyKey      ed25519.PublicKey

	// MCP handler — set externally for xpo serve mode.
	MCPHandler http.Handler

	// Proxy mode — when set, /api/* routes are reverse-proxied to
	// the remote server with the bearer token injected.
	ProxyURL   string
	ProxyToken string

	// SSE hub for real-time event notifications.
	SSEHub *SSEHub
	// contains filtered or unexported fields
}

Server holds the state for the xpo web server.

func NewServer

func NewServer(cfg *config.Config, port int, devMode bool, devPort int) *Server

NewServer creates a new Server instance.

func (*Server) AddPendingEvent

func (s *Server) AddPendingEvent(evt model.Event)

AddPendingEvent adds an event to the pending buffer.

func (*Server) Bind

func (s *Server) Bind() (net.Listener, error)

Bind acquires a TCP listener on s.Port, falling back to the next free port if that one is taken. The actual port bound is written back to s.Port.

func (*Server) BroadcastEvent

func (s *Server) BroadcastEvent(eventType, issueID string)

BroadcastEvent is the exported form of broadcastEvent for use by external callers (e.g. MCP tool handlers that need to push SSE notifications).

func (*Server) DiscardPending

func (s *Server) DiscardPending()

DiscardPending clears all pending events.

func (*Server) GetAllEvents

func (s *Server) GetAllEvents() ([]model.Event, error)

GetAllEvents returns all persisted events plus pending events. The persisted events are cached and only re-read when the database file has changed on disk.

func (*Server) GetPendingCount

func (s *Server) GetPendingCount() int

GetPendingCount returns the number of pending (unsaved) events.

func (*Server) GetProjectedIssues

func (s *Server) GetProjectedIssues() (map[string]*model.Issue, error)

GetProjectedIssues returns all issues with pending events applied. Results are cached and recomputed only when the on-disk database or the pending-events buffer has changed.

func (*Server) SaveAndSync

func (s *Server) SaveAndSync(commitMessage string) error

SaveAndSync persists pending events to storage and optionally commits to git.

func (*Server) ServeOn

func (s *Server) ServeOn(l net.Listener) error

ServeOn serves HTTP on the provided listener. Use after Bind.

func (*Server) SetupRoutes

func (s *Server) SetupRoutes() *http.ServeMux

setupRoutes configures the HTTP routes for the server.

func (*Server) Start

func (s *Server) Start() error

Start binds and serves in one step. Equivalent to Bind followed by ServeOn.

func (*Server) WatchDB

func (s *Server) WatchDB()

WatchDB watches issues.db for external modifications and broadcasts an SSE event so the GUI refreshes immediately.

func (*Server) WatchGitRefs

func (s *Server) WatchGitRefs()

WatchGitRefs watches .git/refs/heads/ (recursively) and .git/packed-refs for changes. When git refs change (new commit, new/deleted branch, gc), the cached issues are invalidated and an SSE update is broadcast so the GUI refreshes branch stats immediately.

type TrendsBlock added in v1.2.0

type TrendsBlock struct {
	Weekly           []WeeklyTrend `json:"weekly"`
	MedianTriageMins int           `json:"median_triage_mins"`
	TriagedCount     int           `json:"triaged_count"`
	BugAge           BugAgeBuckets `json:"bug_age"`
}

type VelocityBucket added in v1.2.0

type VelocityBucket struct {
	WeekStart string `json:"week_start"` // YYYY-MM-DD (Monday)
	Points    int    `json:"points"`
}

type WeeklyTrend added in v1.2.0

type WeeklyTrend struct {
	WeekStart string `json:"week_start"`
	Created   int    `json:"created"`
	Completed int    `json:"completed"`
}

type WorkloadEntry added in v1.2.0

type WorkloadEntry struct {
	Assignee      string `json:"assignee"`
	InProgress    int    `json:"in_progress"`
	OpenPoints    int    `json:"open_points"`
	Blocked       int    `json:"blocked"`
	LastCompleted string `json:"last_completed,omitempty"` // YYYY-MM-DD
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL