web

package
v0.5.2 Latest Latest
Warning

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

Go to latest
Published: Oct 16, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package web is the review portal 'jaira web' serves: a page in the browser for a person who wants to see and sort the work without an agent and without the terminal board.

There is no server in the deployment sense. The binary listens on loopback for as long as the command runs, and everything it shows comes from the same files and refs the CLI reads; sync stays git, a fetch in the background and a push after each change. That is also why this package holds no data of its own: a Source hands it the epics and the tickets, and the command that starts it decides where they come from.

The page is a Svelte build under ui/dist, committed and embedded, so a 'go install' without Node still ships it. CI rebuilds it from ui/src and fails when the committed files differ.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNotFound = errors.New("not found")
	ErrExists   = errors.New("exists already")
	ErrInvalid  = errors.New("invalid")
	ErrConflict = errors.New("does not apply")
)

The errors a Source wraps so the handler answers with what went wrong rather than 500: no such epic or ticket, a name already taken or not usable, and a change that does not apply (a follow-up has no member line to take out).

Functions

func Listen

func Listen(port int) (net.Listener, error)

Listen opens a listener on loopback. port 0 picks a free one.

func OpenBrowser

func OpenBrowser(url string) error

OpenBrowser opens url in the default browser and does not wait for it.

It is best effort: a machine with no browser (a container, an ssh session) still gets the address printed by the caller, which is the fallback a person can always act on.

Types

type Check

type Check struct {
	Text string `json:"text"`
	Done bool   `json:"done"`
}

Check is one item of a ticket's definition of done.

type Epic

type Epic struct {
	Name        string   `json:"name"`
	Title       string   `json:"title"`
	Description string   `json:"description"`
	Done        int      `json:"done"`
	Total       int      `json:"total"`
	Tickets     []Ticket `json:"tickets"`
}

Epic is one function of the product with its progress.

type Note

type Note struct {
	At     time.Time `json:"at"`
	Author string    `json:"author"`
	Text   string    `json:"text"`
}

Note is one progress note; Text is markdown as its author wrote it.

type Server

type Server struct {
	Source Source
	// Fetch brings the remote's refs into this clone; Push sends what Assign
	// queued. Either may be nil on a board with no remote.
	Fetch func() error
	Push  func() error
	// contains filtered or unexported fields
}

Server serves the API and the embedded page.

func (*Server) FetchNow

func (s *Server) FetchNow()

FetchNow runs one fetch and records its outcome. It is what the background loop calls every interval.

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler returns the routes: the JSON API under /api/ and the page for everything else.

Every request must name a loopback host. Binding to 127.0.0.1 keeps other machines out, but not a web page in the same browser: through DNS rebinding a foreign site can reach a port on loopback under its own host name. Checking Host refuses that, and checking Origin on writes refuses a cross-site form post.

func (*Server) Loop

func (s *Server) Loop(every time.Duration, stop <-chan struct{})

Loop fetches every interval until stop is closed. The first fetch runs at once, so the page opened right after the command shows today's state.

func (*Server) SyncState

func (s *Server) SyncState() Sync

SyncState returns a copy of the sync record.

type Source

type Source interface {
	Epics() ([]Epic, error)
	Unassigned() ([]Ticket, error)
	Ticket(id string) (TicketDetail, error)
	CreateEpic(name, title, description string) error
	// Assign puts a ticket into epic; with from set it takes it out of that
	// epic as well, which is a move.
	Assign(epic, from, ticketID string) error
	Unassign(epic, ticketID string) error
}

Source is where the page's data comes from and where a change goes.

The writes only record the change locally (and queue its ref); sending it is Push's job, so a remote that is down never makes the change itself fail.

type Sync

type Sync struct {
	LastFetch  time.Time `json:"last_fetch,omitzero"`
	FetchError string    `json:"fetch_error,omitzero"`
	LastPush   time.Time `json:"last_push,omitzero"`
	PushError  string    `json:"push_error,omitzero"`
}

Sync is what the page says about the last round trip to the remote.

type Ticket

type Ticket struct {
	ID     string `json:"id"`
	Handle string `json:"handle"`
	Title  string `json:"title"`
	Lane   string `json:"lane"`
	Done   bool   `json:"done"`
	// Direct is false for a ticket that belongs to an epic only because it
	// follows, or is a child of, one of its members.
	Direct bool `json:"direct"`
}

Ticket is one card on the page.

type TicketDetail

type TicketDetail struct {
	ID      string  `json:"id"`
	Handle  string  `json:"handle"`
	Title   string  `json:"title"`
	Lane    string  `json:"lane"`
	Goal    string  `json:"goal"`
	Context string  `json:"context"`
	DoD     []Check `json:"dod"`
	Notes   []Note  `json:"notes"`
}

TicketDetail is what the page shows of one ticket when it is opened.

Jump to

Keyboard shortcuts

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