inboxes

package
v0.2.0 Latest Latest
Warning

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

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

README

Inboxes

An inbox is a saved filter over the desktop app's Up next queue. People define inboxes and the app lists them under Inbox in the sidebar, each showing the queue entries that match. This package only stores the definitions in the daemon and serves them at /api/inboxes, so they follow the person across app restarts; internal/inboxqueue matches them against the queue for the app (/api/inbox). Per-inbox notifications are a later step.

An inbox

Field Values
id assigned by the daemon (random hex), stable across saves
name 1 to 40 characters after trimming, unique ignoring case
color blue, green, red, amber, purple, grey
what any, review, merge, agent, mine
owners GitHub owner logins (organization or user), at most 50; empty means any
risk any, 3, 4 (strings)
repositories owner/name, at most 50; empty means every repository that matches
query a filter query the app parses and matches, trimmed, one line of at most 500 characters; empty (or missing) for none. The app reads what, owners, risk, repositories and no_bots as part of it and saves them folded in, leaving those fields at their plain values
no_bots leave out pull requests by bot authors (author_bot in discovery)
badge show a count badge in the sidebar

At least one and at most 20 inboxes. The array order is the sidebar order. Owners and repositories are compared and de-duplicated ignoring case; the first spelling given is kept.

Storage

The state/inboxes document in the shared private app.sqlite application database:

{"revision": 3, "initialized": true, "inboxes": [{"id": "...", "name": "Platform", "color": "blue", "what": "any", "owners": ["baum-ai"], "risk": "any", "repositories": ["baum-ai/baum"], "no_bots": false, "badge": true}]}

initialized marks that the default inbox has been dealt with. The first read (or save) of a document without it initializes it in one immediate transaction, so independent stores and concurrent clients agree:

  • A fresh installation gets one inbox named Default: what any, no owners, risk any, no repositories, query author=@me OR assignee=@me OR review-requested=@me (the pull requests the person wrote, is assigned or was asked to review; an inbox with an empty query lists every open pull request), no_bots false, badge on, color blue, with a daemon-assigned id. The revision becomes 1.
  • An upgrade with stored inboxes adds no Default. The app used to show only the Up next queue in an inbox (the person's review requests, own pull requests and assigned pull requests), and an empty query now means every open pull request, so each stored inbox keeps what it showed: its entry is carried over as stored JSON, re-encoded compactly, with id, name, color, order and all other fields unchanged and only query rewritten. An empty query becomes the default query. A query q becomes (<default query>) (q); the parentheses keep a top-level OR in q inside it. If that exceeds 500 characters it becomes waiting!=other (q) (the app's filter for "not someone else's pull request that waits on nothing from the person"), and if that is too long as well the query is left unchanged. The revision increments and the document is marked initialized, so this runs once. An upgrade from a document with no inboxes gets Default as above.
  • Default is an ordinary inbox: it can be renamed, edited or deleted while another inbox remains, and is not recreated afterwards.
  • A saved list is never empty: the last inbox cannot be deleted.
  • GET never changes an initialized document (a racing first read may rewrite identical bytes).

A document that does not parse is never overwritten: GET and PUT answer 500 inboxes_unreadable. Fix or remove the document to continue. Writes compare the revision and replace the document in one immediate SQLite transaction, including across independent store instances.

API

  • GET /api/inboxes answers {"available": true, "revision": n, "initialized": true, "inboxes": [...]}.
  • PUT /api/inboxes takes {"revision": n, "inboxes": [...]} and replaces the whole list, answering the saved list in the shape of GET. revision is the one the client read; any other answers 409 stale_revision with the current list, including a client still holding revision 0 on a fresh installation. Each save increments the revision. An entry without an id gets one. An id in the daemon's shape (32 lower-case hex characters) is kept even when the stored list no longer holds it, so an inbox deleted and brought back by Undo keeps its links; any other id is refused. Other methods answer 405.

Errors are {"code": "...", "message": "..."} with these codes. A refused save changes nothing.

Status Code Meaning
400 invalid_request the body is not one JSON object of the expected shape, has no revision, or is over 256 KB
400 invalid_inbox a field is out of range: an id the daemon did not assign, name length, color, what, risk, owner or repository shape, too many owners or repositories, or a query that is too long or has more than one line
400 too_many_inboxes more than 20 inboxes
400 last_inbox an empty list: at least one inbox must remain
400 duplicate_name two names equal ignoring case
400 duplicate_id the same id twice
409 stale_revision the revision is not the stored one; the body carries the current list
500 inboxes_unreadable the stored file cannot be parsed; it is left untouched

Documentation

Index

Constants

View Source
const (
	MaxInboxes      = 20
	MaxNameLength   = 40
	MaxOwners       = 50
	MaxRepositories = 50
	MaxQueryLength  = 500
)

Limits and the allowed values of the enumerated fields.

View Source
const (
	CodeInvalidRequest = "invalid_request"
	CodeInvalidInbox   = "invalid_inbox"
	CodeTooMany        = "too_many_inboxes"
	CodeLastInbox      = "last_inbox"
	CodeDuplicateName  = "duplicate_name"
	CodeDuplicateID    = "duplicate_id"
	CodeStale          = "stale_revision"
	CodeUnreadable     = "inboxes_unreadable"
)

Error codes of the API.

View Source
const DefaultName = "Default"

DefaultName is the name of the inbox every installation starts with.

View Source
const DefaultQuery = "author=@me OR assignee=@me OR review-requested=@me"

DefaultQuery is the query of the default inbox: the pull requests the person wrote, is assigned or was asked to review.

View Source
const QueueScope = "waiting!=other"

QueueScope is the app's filter for "not someone else's pull request that waits on nothing from the person". It is the shorter form of DefaultQuery that upgrades use when the combined query would be too long.

Variables

This section is empty.

Functions

func ValidQuery

func ValidQuery(query string) bool

ValidQuery reports whether a filter query is one line of valid UTF-8 text of at most MaxQueryLength characters. The daemon never parses it: the app does.

Types

type API

type API struct {
	Store *Store
	// Changed is called after every successful save.
	Changed func()
}

API serves /api/inboxes. It has no authentication; the daemon mounts it behind its authenticated host.

func (*API) Handler

func (h *API) Handler() http.Handler

Handler routes /api/inboxes.

type Inbox

type Inbox struct {
	ID           string   `json:"id"`
	Name         string   `json:"name"`
	Color        string   `json:"color"`
	What         string   `json:"what"`
	Owners       []string `json:"owners"`
	Risk         string   `json:"risk"`
	Repositories []string `json:"repositories"`
	Query        string   `json:"query"`
	NoBots       bool     `json:"no_bots"`
	Badge        bool     `json:"badge"`
}

Inbox is a saved filter over the app's Up next queue. This package stores it; internal/inboxqueue matches it.

type InvalidError

type InvalidError struct {
	Code    string
	Message string
}

InvalidError is a refused request: a stable code and a message for a person.

func (*InvalidError) Error

func (e *InvalidError) Error() string

type StaleError

type StaleError struct{ Current State }

StaleError is a replace based on an older revision than the stored one.

func (*StaleError) Error

func (*StaleError) Error() string

type State

type State struct {
	Revision int     `json:"revision"`
	Inboxes  []Inbox `json:"inboxes"`
	// Initialized is set once the default inbox has been created, so that
	// deleting it later is not undone.
	Initialized bool `json:"initialized"`
}

State is the stored list and the revision it is at.

type Store

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

Store keeps inbox definitions in the application database.

func Open

func Open(path string) *Store

Open returns a store backed by the application database at path.

func (*Store) Read

func (s *Store) Read() (State, error)

Read returns stored state. The first read of an installation creates the default inbox.

func (*Store) Replace

func (s *Store) Replace(revision int, inboxes []Inbox) (State, error)

Replace saves the whole list, based on the revision the caller read. Entries without an id get one; an id in the daemon's shape is kept, stored or not.

type UnreadableError

type UnreadableError struct{ Err error }

UnreadableError reports stored state that cannot be read. It is never overwritten, so what it holds is not lost silently.

func (*UnreadableError) Error

func (e *UnreadableError) Error() string

func (*UnreadableError) Unwrap

func (e *UnreadableError) Unwrap() error

Jump to

Keyboard shortcuts

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