Documentation
¶
Overview ¶
Package board holds aeman's pure board logic, ported from the web frontend: date helpers, the stage model, per-team sprint pointers, the board views (Team grid, Me, Triage) and the status/progress transition rules. Every function works on an in-memory Board snapshot with no network access, so the HTTP API and the MCP server can later expose the same views and actions.
Index ¶
- Constants
- Variables
- func ActiveOnDay(start, finish, day string) bool
- func ActiveSprint(b Board, team, day string) string
- func ActorFrom(ctx context.Context) string
- func AddDays(iso string, delta int) string
- func CapacityOfPerson(b Board, login string) int
- func CarryingNow(b Board, today string) map[string]int
- func ClampProgress(stage StageKey, value int) int
- func ColumnDomain(b Board, project, epic string) (string, bool)
- func Complete(stage StageKey, progress int) bool
- func CurrentSprint(b Board, team string) string
- func CycleWindow(task Card, week string) (string, string)
- func DateRange(start, end string) string
- func DaysBetween(from, to string) int
- func DaysSince(iso, asOf string) int
- func DerivedProgress(children []Card) int
- func DomainFrom(ctx context.Context) string
- func DomainOf(c Card, r DomainResolver) string
- func DueDate(c Card) string
- func DueInWeek(recurrence, start, week string) bool
- func EndOfDay(day string) (time.Time, error)
- func FileDomain(b Board, c Card) string
- func FormatEventBody(e Event) string
- func HasRecords(view string) bool
- func HomeDomain(b Board, c Card) string
- func InBacklog(c Card) bool
- func InEpic(c Card, project, name string) bool
- func InProject(c Card, project string) bool
- func IsDayIso(s string) bool
- func IsInProgress(c Card) bool
- func IsLegacyID(id string) bool
- func IsPersonalDomain(name string) bool
- func IsRecord(c Card, past map[string]bool) bool
- func IsStateTitle(title string) bool
- func IsUnattributed(ctx context.Context) bool
- func LoadNow(b Board, today string) map[string]int
- func LocalDateIso(iso string) string
- func Location() *time.Location
- func LocationName() string
- func Mirrored(c Card, project, epic string) bool
- func MondayOf(iso string) string
- func NeedsTriage(_ Board, c Card, _ string) bool
- func NextAfter(cycle, anchor, from string) string
- func OpenChildren(b Board, itemID string) bool
- func Overdue(c Card, today string) bool
- func PartitionEvents(notes []Note) ([]Note, []Event)
- func PersonalDomain(login string) string
- func PersonalLeaves(c Card, today string) bool
- func PersonalOwner(name string) string
- func PlacedAhead(c Card, today string) bool
- func Points(s SizeKey) int
- func PointsAWeekOf(b Board, team string) int
- func PointsOf(b Board, c Card) int
- func PreviousSprint(b Board, team string) string
- func ProcessDomain(b Board, name string) string
- func ProjectDomain(b Board, project string) string
- func RankBetween(prev, next string) (string, error)
- func RankRebalance(lo, hi string, n int) ([]string, error)
- func RankTooLong(key string) bool
- func Reachable(b Board, today string) map[string]bool
- func RecurrenceDue(c Card, day string) bool
- func RenderNoteBody(author, text string) string
- func RosterConflict(b Board, team, project string) (teamDomain, projectDomain string, conflict bool)
- func SetLocation(name string) error
- func SplitNoteAuthor(body string) (author, text string)
- func StripEventLines(text string) string
- func TeamDomain(b Board, team string) string
- func TeamsPast(live Board, day string) map[string]bool
- func TodayIso() string
- func TriageWeekOf(_ Board, c Card, _ string) string
- func Unattributed(ctx context.Context) context.Context
- func UpcomingRecurrences(b Board, c Card, from string, weeks int) []string
- func UpcomingTurns(b Board, task Card, from string, weeks int) []string
- func ValidRecurrence(cycle string) bool
- func WeeksCovered(c Card) []string
- func WithActor(ctx context.Context, login string) context.Context
- func WithDomain(ctx context.Context, domain string) context.Context
- func Workable(c Card) bool
- type Board
- type Capacity
- type Card
- func BacklogOrder(cards []Card) []Card
- func Children(b Board, itemID string) []Card
- func Followers(b Board, itemID string) []Card
- func Iterations(b Board, taskID string) []Card
- func MeView(b Board, user, day string) []Card
- func PersonalReseed(b Board, login, day string) []Card
- func PersonalView(b Board, login, day string) []Card
- func TasksOf(b Board, process string) []Card
- func TeamGrid(b Board, team, day string) []Card
- func Unreachable(b Board, today string) []Card
- type CreateInput
- type Deadline
- type DomainResolver
- type EpicCol
- type Event
- type Link
- type Note
- type Person
- type Placement
- type Process
- type SizeKey
- type SprintState
- type StageDef
- type StageKey
- type ZoneKey
Constants ¶
const ( EventCreated = "created" EventStage = "stage" EventProgress = "progress" EventAssignee = "assignee" EventTeam = "team" EventEpic = "epic" EventMirror = "mirror" EventProcess = "process" EventZone = "zone" EventSize = "size" EventReviewSent = "review-sent" EventReviewPassed = "review-passed" EventReviewerRemoved = "reviewer-removed" EventDates = "dates" EventSprint = "sprint" EventWeek = "week" EventBacklog = "backlog" EventReviewRound = "review-round" EventParent = "parent" EventSubtask = "subtask" EventRecurrence = "recurrence" // EventLeft: a personal card left behind on a past day's board by the × // (to = that day), or brought back by re-dating it (to = ""). EventLeft = "left" )
Event kinds written by the board service.
const ( RecurrenceSprint = "" // every sprint (default) RecurrenceWeek = "week" // reseed when the new sprint is ≥1 week past the card's sprint RecurrenceFortnight = "2weeks" // ≥2 weeks past RecurrenceMonth = "month" // ≥1 calendar month past RecurrenceQuarter = "quarter" // ≥3 calendar months past )
Recurrence cycles: how often a finished recurrent card reseeds a fresh copy at carry-over. The empty default keeps the original behaviour — every sprint.
const DeadlineStateTitle = "aeman:deadline-state"
DeadlineStateTitle marks the hidden card that puts a deadline on a week of the Project board — the line across the grid. One card per deadline: its Week is the week the line sits on and its Project is whose deadline it is. A project holds at most one deadline per week, so two of ITS OWN lines dragged onto the same week merge into one; two projects can of course both have something due that week.
const DefaultBarColor = "#3fb950"
DefaultBarColor is the progress-bar colour for a card with no stage, mirroring DEFAULT_BAR_COLOR.
const DefaultSize = SizeM
DefaultSize is what a card nobody has sized WEIGHS on a board: M.
Not zero. A board that weighs unsized work as nothing tells a person their week is empty while they are drowning in it, and the number goes up as the cards are sized — which reads as the sizing having caused the load. M is the middle of the board's own record: of 2194 sized cards on the production board 40% are S, 34% M, 23% L, 1% XL — the median card is M and the mean is 2.14 points — and assuming M costs less than any other guess (mean error 0.96 points against 1.14 for S and 1.97 for L). The cards most likely to be left unsized, the ones with no description at all, average 1.86 points, which is nearer M than S too.
const EpicStateTitle = "aeman:epic-state"
EpicStateTitle marks the hidden card that declares an epic column of the Project board. One card per epic: its Epic field is the epic's name, its Project field is the project the epic belongs to, and its position on the board is the column order (exactly the team-roster mechanism of sprint-state cards). The card exists so an empty epic — just added, or emptied out — still has a column.
const MaxRankLen = 32
MaxRankLen is the length past which a key asks for a rebalance.
const ProcessStateTitle = "aeman:process-state"
ProcessStateTitle marks the hidden card that declares a process — recurring work the team keeps doing and wants to see itself doing: its Process field is the name, its Project the plan it is part of. A process groups tasks the way a project groups epic columns.
const ProcessTaskTitle = "aeman:process-task"
ProcessTaskTitle marks the hidden card an iteration is copied FROM. Its Process names the process, its Title and Description are what the iteration will say, Recurrence is its cycle, StartDate the calendar anchor the cycle is counted from, Team the plan the iterations land in, and Assignees the standing owner. Unlike a recurrent card's reseed, which copies the previous iteration (so a rename propagates forever), every iteration comes from here — the live card may be renamed or described freely.
const ProjectStateTitle = "aeman:project-state"
ProjectStateTitle marks the hidden card that declares a project — the Project board's top-level grouping, one step above epics. One card per project: its Project field is the name and its board position is the order the chips appear in. Like an epic, a project exists in its own right, so an empty one can be created first and filled with epics afterwards.
const SprintStateTitle = "aeman:sprint-state"
SprintStateTitle marks the hidden per-team card that stores a team's sprint pointer (current/previous start dates). It mirrors SPRINT_STATE_TITLE in web/src/providers/github/githubProvider.ts; such cards never render on a board.
Variables ¶
var ErrRankOrder = errors.New("rank: neighbours are equal or inverted")
ErrRankOrder is returned when the neighbours handed in are not in order.
SizeOrder is the four sizes smallest first — the order a picker shows them.
var StageOrder = []StageKey{StageLocked, StageReview, StageRecurrent, StageRefuse, StageDone}
StageOrder is the canonical stage ordering, mirroring STAGE_ORDER.
var Stages = map[StageKey]StageDef{ StageLocked: {Key: StageLocked, Label: "Locked", Color: "#cf222e"}, StageReview: {Key: StageReview, Label: "Review", Color: "#d4a72c"}, StageRecurrent: {Key: StageRecurrent, Label: "Recurrent", Color: "#58a6ff"}, StageRefuse: {Key: StageRefuse, Label: "Refuse", Color: "#1f2328"}, StageDone: {Key: StageDone, Label: "Done", Color: "#1f883d"}, }
Stages maps each stage to its label and bar colour, mirroring STAGES.
Functions ¶
func ActiveOnDay ¶
ActiveOnDay reports whether `day` falls within a card's [start, finish] range. A missing bound collapses to the other date, so a card with a single date is active only on that day and a card with neither date is never on the day board. It mirrors activeOnDay in web/src/date.ts (from = start||finish, to = finish||start, from <= day <= to).
func ActiveSprint ¶
ActiveSprint returns which sprint was current for a team on a given day: the team's current sprint when day is on or after it, else the previous sprint when day is on or after that, else "" (only the last two sprints are tracked). The Me view groups a day's cards by this. It mirrors activeSprint in web/src/sprint.ts. team = "" is the no-team group.
func AddDays ¶
AddDays shifts a yyyy-mm-dd date by delta days, returning yyyy-mm-dd, or `iso` unchanged when it is not a parseable date. It mirrors addDays in web/src/date.ts.
func CapacityOfPerson ¶ added in v0.37.0
CapacityOfPerson is how many points a week a person gets through: the roster's number, and only that. 0 means nobody has set one, which a client draws as the load alone rather than as a limit of none.
The board deliberately does NOT derive a number from its own record. It can be derived — the median of the points closed in each of the last four complete weeks the person closed anything in — and that arithmetic is written down in the derive-capacity skill, for a lead to run and write the answer back like any other judgement. It is not run here because a number the board prints beside a name reads as a fact, and this one would not be one: doneAt is only written from the day a board starts keeping it, so the four-week window is mostly empty of RECORDS while being full of WORK, and the median of the little that is there lands far under the truth — and lands quietly, since nothing on screen says the window was half empty. One number somebody stands behind beats four weeks of arithmetic over a record that does not go back four weeks.
func CarryingNow ¶ added in v0.32.0
CarryingNow is how much work a person is holding at this moment, counted across EVERY team.
A board is nearly always read through a filter — one team's columns, one project's grid — and a person is not. Somebody with four cards in the team on screen may have eleven altogether, and handing them a fifth is a decision made in the dark unless the whole number is in front of the reader. So this one ignores the filter on purpose.
What counts is work that is theirs, open, and not put off to a week that has not arrived: a card placed ahead is on no day board until its Monday (B1) and is not what they are carrying today. A subtask rides its parent and is not a card of its own; a state card is the board's own bookkeeping; a personal board's card is nobody else's business.
func ClampProgress ¶
ClampProgress clamps a raw progress value for a card in the given stage. A card that is review, locked or REFUSED is held within the 10–90% band (never 0% or 100%); any other stage, including StageNone, keeps the value unchanged. It mirrors the slider clamp shared by handleProgress and the Card component.
All three are work that is neither untouched nor finished: it is parked on somebody else's answer, and a bar at either end would read as one of those two.
func ColumnDomain ¶ added in v0.29.0
ColumnDomain is the repository a COLUMN belongs to — the domain of the epic stub that declares it, not of its project. The two agree wherever a project owns the column (a project and its columns live together), but the NO-PROJECT bucket is a real column with no project to ask: reading the project there answers "no such repository" for every card in it. Reports false only for a column the roster does not declare at all.
func CurrentSprint ¶
CurrentSprint returns a team's current sprint start from its sprint-state card, or "" when the team has no sprint yet. team = "" is the no-team group. It mirrors currentSprint in web/src/sprint.ts.
func CycleWindow ¶ added in v0.32.0
CycleWindow is the occurrence containing `week`: the first and last week a turn of it may stand in, both Mondays and both inclusive. Empty strings when the task has no calendar to reckon with — no recurrence, a per-sprint one (sprints are not dates), or an anchor nobody can read.
It is what bounds the grip on the Triage board: a turn moves inside its own occurrence, because a turn carried past the next due date would stand where the next turn belongs and the two would read as one process running twice.
func DateRange ¶
DateRange renders a start..end pair for a dates event value ("" parts kept readable: "..2026-07-04", "2026-07-01..", "" when both empty).
func DaysBetween ¶ added in v0.32.0
DaysBetween is the signed number of days from one ISO day to another — negative when `to` is the earlier one. Mirrors daysBetween in web/src/date.ts.
func DaysSince ¶
DaysSince returns the whole days between an ISO timestamp's local date and the `asOf` day (a yyyy-mm-dd string; "" means today). It never goes negative. It mirrors daysSince in web/src/date.ts.
func DerivedProgress ¶
DerivedProgress computes a parent's progress from its subtasks: the mean of each child's effective progress (a complete child counts as 100), scaled into the 0..90 band — the final done/100% is always a human's call.
func DomainFrom ¶ added in v0.26.0
DomainFrom is the caller's choice of domain, or "" for the default.
func DomainOf ¶ added in v0.26.0
func DomainOf(c Card, r DomainResolver) string
DomainOf applies the rule, in order:
- a review card lives where the card it reviews lives — its team is the original's and its project is empty, so the team rule would leak a review of a closed card into the shared repository;
- a subtask lives where its parent lives, whatever column it carries;
- an iteration lives where its task lives;
- an unlinked card filed under a project lives where the project lives;
- any other card lives where its team lives.
An unknown reference falls through to the next rule; nothing deciding means the primary domain, "".
func DueDate ¶ added in v0.19.0
DueDate is the day a card that came from a plan was owed by — "" for a card that has no such day. Three kinds of card come from a plan, and each has its own clock:
- a Project-board slot is owed by its end date;
- a process turn is owed by the end of the week it was filed in;
- a card scheduled for a WEEK is owed by the end of that week.
A card with no week is not scheduled work and has no due date here: the day board's carry-over is how those move, and it is not this rule's business.
func DueInWeek ¶ added in v0.32.0
DueInWeek reports whether a process task comes due in the week beginning on `week`: the first turn falling after the day before it lands on or before the week's end. It is the calendar question spawnIfDue asks before filing anything, and asking it the same way is what keeps a projection honest — the week the board draws a turn in is the week the sweep will file it in.
func EndOfDay ¶ added in v0.30.0
EndOfDay is a board day's last moment in the BOARD's own time zone — the instant a record of that day reflects. The day belongs to everyone on the board, so it is measured where the board lives, not where the reader does.
func FileDomain ¶ added in v0.29.0
FileDomain is the repository whose files hold this card, read in the board's ONE namespace: the stamp the store put on it — gitstore stamps every card with its domain's name, the primary's included — with an unstamped card read as the primary.
It answers where the card IS, which is the question a rule about the references written IN its file has to ask (a process tie names a process of the repository the file sits in). HomeDomain answers where a card WOULD live by the placement rule, which is what a CHANGE is judged against. Comparing one against a raw stamp is how a named primary and an unstamped entry became two repositories.
func FormatEventBody ¶
FormatEventBody renders an event as the body of a log line (the part after the "- [timestamp] " header): ":: kind | actor | from | to". Pipes inside values are replaced — they are the field separators.
func HasRecords ¶ added in v0.30.0
HasRecords reports that a view can be shown AS IT STOOD on a day: only the day boards can, because only they place a card by the day being looked at. The Project board lays every week out at once and the Process tab reads a structure — a day means nothing to either, so a record of one would be a claim about nothing. Mirrored by snapshotDay in web/src/viewquery.ts.
func HomeDomain ¶ added in v0.29.0
HomeDomain is the repository that HOLDS a card, in the same namespace: the placement rule's answer (linked cards first, G14), with "nothing places this card" read as the primary — which is where such a card's file goes.
func InEpic ¶ added in v0.17.0
InEpic reports whether a card is filed under a column. Both halves have to match: the same epic name in another project is a different column.
func InProject ¶ added in v0.29.0
InProject reports whether a card stands in one project's columns — its home pair, or ANY mirror it carries. A mirror is the same card standing in a second column (G15), so a project's listing that read only the home pair answered with less than the board it draws.
func IsDayIso ¶ added in v0.26.6
IsDayIso reports whether s is a board day: yyyy-mm-dd, and a real date.
func IsInProgress ¶
IsInProgress reports the implicit "In Progress" status: a card with no stored stage whose progress sits in [10, 90] inclusive. It mirrors isInProgress in web/src/stages.ts and is deliberately not a StageKey (there is no stored option for it).
func IsLegacyID ¶ added in v0.26.0
IsLegacyID reports whether id is a Projects v2 item id — the form cards had before the move to git. A migrated card keeps its old id as GitHubID, and the service resolves such an id to the card for one major version.
func IsPersonalDomain ¶ added in v0.26.0
IsPersonalDomain reports whether a domain name is a personal one.
func IsRecord ¶ added in v0.30.0
IsRecord reports that the day is over for this card: its team's sprint has moved past it, so what the card shows is a record and cannot be changed. Every door asks THIS — the merge that builds the day, and the guard that refuses a write made from it — or the two answer differently and a card is live on screen and refused by the server.
A PERSONAL card is never a record: it belongs to no team and no sprint, its day comes from its own dates, and it lives in its owner's repository. Named by an empty team, it would otherwise answer to the no-team GROUP's sprint, which it shares nothing with but that empty name.
func IsStateTitle ¶ added in v0.26.0
IsStateTitle reports whether a title marks a roster stub — the hidden cards NewBoard splits out of the card rows.
func IsUnattributed ¶ added in v0.11.2
IsUnattributed reports whether ctx was marked by Unattributed.
func LoadNow ¶ added in v0.37.0
LoadNow is CarryingNow in points: the same cards a person is carrying today — theirs, open, not put off to a week ahead, subtasks riding their parent — weighed with PointsOf instead of counted. Like CarryingNow it ignores the filter on purpose: a person is not read through one, and the number beside their name has to be the whole of it. Unsized cards weigh the default, so the number is honest on a board nobody has sized yet.
func LocalDateIso ¶
LocalDateIso returns the local yyyy-mm-dd date for an ISO timestamp, or "" when the timestamp cannot be parsed. It mirrors localDateIso in web/src/date.ts.
func Location ¶ added in v0.26.6
Location is the board's time zone — what a board day's first moment is measured in (the day feed's history boundary, for one).
func LocationName ¶
func LocationName() string
LocationName reports the installed board time zone (IANA name or "Local").
func Mirrored ¶ added in v0.28.0
Mirrored reports whether the pair is one of the card's MIRROR placements — not its home. InEpic answers "does the card stand in this column"; promotion, unmirroring and duplicate checks need the narrower question.
func MondayOf ¶
MondayOf returns the yyyy-mm-dd Monday of the week containing `iso`, or `iso` unchanged when it is not a parseable date. It mirrors mondayOf in web/src/date.ts.
func NeedsTriage ¶ added in v0.32.0
NeedsTriage reports whether nobody has said WHEN the card's work is due: an open card of its own (no parent, no original) with no week (B5). The week is the whole of the decision — a card on today's board was put there by the day's planning, not by a week's, and until someone gives it a week it is work of unknown time. That is the pile the strip exists to show, however large it is at first.
A card SENT TO REVIEW is not in that pile either. Its work is done and it is waiting on a reviewer, not on a week: asking the strip's reader for one asks them to decide something nobody is waiting on them to decide.
func NextAfter ¶ added in v0.18.0
NextAfter is the first date strictly after `from` at which a cycle anchored on `anchor` comes due — the calendar arithmetic behind process tasks. The cycle is counted on the calendar from the anchor, never from when the last iteration happened to close, so a late March does not shift April. The per-sprint default has no calendar meaning and yields "".
func OpenChildren ¶
OpenChildren reports whether any of a card's subtasks is still unfinished.
func Overdue ¶ added in v0.19.0
Overdue reports whether a card has broken a promise somebody else is holding — which is what the mark means, and why only two kinds of card can carry it: a Project-board SLOT, owed by the end date its row was drawn to, and a process TURN, owed by the end of the week its process filed it into. Both are commitments made on another board, by somebody who is not the person the card sits on.
A card scheduled into a week on the Triage board is not one of those. Its week is that board's own planning, and planning is what the next sync redoes — so calling it late for being open on Monday paints most of a normal board red, and a mark that is everywhere says nothing where it matters. Such a card is not marked and is not hidden either: a week gone by holds its own record, and the day boards go on drawing the work until it is finished (TeamGrid holds back the weeks AHEAD, never the ones behind).
It is derived, never stored — the card's own dates are the truth, and a flag beside them would be one more thing to drift.
func PartitionEvents ¶
PartitionEvents splits a parsed log into plain work notes and events: any note whose body is an event line becomes an Event carrying the note's id and timestamp. The relative order inside each slice is preserved.
func PersonalDomain ¶ added in v0.26.0
PersonalDomain is the name of a person's personal domain: their own repository, attached to the board for them alone.
func PersonalLeaves ¶ added in v0.26.0
PersonalLeaves reports whether the × on a personal card leaves it behind on yesterday's board instead of deleting it — the personal analogue of a team card demoting to the previous sprint. A card that has been worked on (progress above 0, finished included) and did not start today has a history worth keeping; an untouched one, or one that started today, has none and is deleted. Never true for a card of any other domain.
func PersonalOwner ¶ added in v0.26.0
PersonalOwner is the login a personal domain belongs to; "" for any other domain.
func PlacedAhead ¶ added in v0.32.0
PlacedAhead reports whether the card is placed in a week ahead of today's. Such a card is on no day board until its Monday (B1): that is what makes the backlog a regulator rather than a list.
func Points ¶ added in v0.37.0
Points is the weight of a size on the scale: 1, 2, 4, 8, and 0 for the empty size. It is the SCALE, not what a card weighs — an unsized card weighs DefaultSize (PointsOf).
func PointsAWeekOf ¶ added in v0.37.0
PointsAWeekOf is a team's weekly capacity in POINTS: the number somebody set for it, and only that. 0 means nobody has said, which a client draws as the week's points alone rather than as a week with no room at all.
It was arithmetic once: the capacities of the PEOPLE in the team, added up, with anyone who works across teams split between them in proportion to what they had closed in each over four weeks — and the total then cut by the share history said arrives unplanned. Three derivations stacked on one record, and on a real board that record is eleven days old, because doneAt is only written from the day a board starts keeping it. One busy week could hand a person's whole number to a team they had barely touched, and nothing on screen said the split rested on four closed cards.
So the board stores what somebody decided and does no arithmetic. Working the number OUT is the derive-capacity skill's job, where the record's own thinness can be read out beside the answer — which a board printing a number beside a name could never do.
func PointsOf ¶ added in v0.37.0
PointsOf is what a card WEIGHS on a board: its own size, or — when it has subtasks somebody has sized — the sum of theirs. This is the umbrella rule, and it is dynamic on purpose: umbrellas are not born as umbrellas (on the production history forty percent get their first child three days or more after creation, and nothing in a card's text predicts which will), so the parent's own estimate stands until the children exist and is replaced by their sum once they do — the way a parent's progress already derives from its subtasks. The total never counts twice, and a card split a minute ago into unsized pieces still weighs what its author said.
func PreviousSprint ¶
PreviousSprint returns a team's previous sprint start from its sprint-state card, or "" when the team has no prior sprint. team = "" is the no-team group. It mirrors previousSprint in web/src/sprint.ts.
func ProcessDomain ¶ added in v0.28.0
ProcessDomain is the repository a process was declared in, read in the board's ONE namespace (inPrimary): an unstamped entry answers with the PRIMARY's name, which is "" only on a board that does not name its own. A process the roster does not declare answers "" and decides nothing.
func ProjectDomain ¶ added in v0.26.9
ProjectDomain is the repository a project was declared in, in the same namespace: the PRIMARY's name for an unstamped entry, "" for a project the roster does not declare — and "" for the empty project NAME, which is not a project at all and decides nothing (SetProcessProject reads the primary itself for that case).
func RankBetween ¶ added in v0.26.0
RankBetween returns a key strictly between prev and next. An empty prev means "before everything", an empty next "after everything"; both empty gives the first key of a list. It never fails on ordered input: when the two are adjacent at their length, the key grows by a character.
func RankRebalance ¶ added in v0.26.0
RankRebalance returns n keys, evenly spaced, strictly between lo and hi (either may be empty for an open end) and none longer than MaxRankLen. The caller assigns them to the run's members in their current order.
func RankTooLong ¶ added in v0.26.0
RankTooLong reports whether a key has outgrown the cap and its run should be rebalanced.
func Reachable ¶ added in v0.31.0
Reachable is every card some board still shows: the day boards for the days a person opens (today, and each team's current and previous sprint day), the Project board's columns, the weekly plan, the Process tab, and a personal board, plus the cards that ride one of those — a subtask under its parent, a review card beside its original.
What it does NOT name is a card on no board at all: open, in no column, no plan week, and standing on none of those days. The × used to make them by the dozen — it demoted a worked card into the previous sprint, out of today's way and into a sprint the day grid does not draw, the Me board's sprint gate hides and no carry-over ever picks up (a carry-over moves the CLOSING sprint's own cards) — and the production board held a hundred and thirty-two when the rule was found. The × deletes now (G60); this is what names the ones already made, for the migration's report and its cleanup.
func RecurrenceDue ¶ added in v0.9.0
RecurrenceDue reports whether a recurrent card's next iteration is due on day (the sprint being started): always for the per-sprint default, and once the interval has elapsed since the sprint the card is bound to otherwise. A cycle card without a sprint anchor is never due — there is nothing to count the interval from.
func RenderNoteBody ¶
RenderNoteBody prepends the author attribution to a note body for storage in the draft log ("@login: text"); an empty author stores the bare text.
func RosterConflict ¶ added in v0.26.9
func RosterConflict(b Board, team, project string) (teamDomain, projectDomain string, conflict bool)
RosterConflict reports a team and a project that live in different repositories, and names both. It is the pair DomainOf cannot honour: the project decides where the card lives (rule 4), so the team's people would not see a card carrying their name while the project's people would. Neither answer is right, so the pair is refused where it is made rather than resolved silently. A name the roster does not declare yet decides nothing — it will be declared in the card's own domain.
func SetLocation ¶
SetLocation installs the board time zone (name per IANA, e.g. "Europe/Berlin"). An unknown name is reported and leaves the zone as-is.
func SplitNoteAuthor ¶
SplitNoteAuthor extracts the "@login: " attribution from a stored note body, returning the author ("" for legacy unattributed notes) and the bare text.
func StripEventLines ¶
StripEventLines drops machine event-log lines from a description text. Descriptions occasionally get them baked in - an agent copying a card's visible text back, a description synced from a card whose body was damaged by an old log migration - and they are never legitimate prose, so every description write runs through this.
func TeamDomain ¶ added in v0.26.9
TeamDomain is the repository a team was declared in, read in the board's ONE namespace (inPrimary): an unstamped entry answers with the PRIMARY's name, which is "" only on a board that does not name its own. A team the roster does not declare answers "" and decides nothing.
func TeamsPast ¶ added in v0.30.0
TeamsPast names the teams for which a day is OVER: their sprint has moved on past it, so what they showed that day is a record and can be read from the history. A team still inside that sprint is not among them — the sprint lays itself out on its own day and the team works it from there, so the day is theirs to change. A team with no sprint at all answers for nothing.
func TriageWeekOf ¶ added in v0.32.0
TriageWeekOf is the Monday of the column a card stands in on the Triage board — its week, and nothing else. A card with no week stands in no column: it is in the strip, waiting for someone to say when.
A PARKED card stands in none either, week or no week. The two are exclusive and every door that gives a week takes the card off its shelf — but the storage is a git repository anything may write to, so a card can arrive carrying both. Drawn by its week it would stand in the grid AND in the drawer: the same work in two places, counted twice against the week. The shelf wins, because the drawer is where such a card can be dealt with.
func Unattributed ¶ added in v0.11.2
Unattributed marks ctx as carrying server-side work with no originating client, so watch frames reach everyone instead of being echo-suppressed against whoever happened to trigger it.
func UpcomingRecurrences ¶ added in v0.32.0
UpcomingRecurrences is every week, of the `weeks` beginning at `from`, in which a recurrent card comes round again and no copy of it stands yet.
The card's OWN week is never among them: the card is standing in it. Nor is a week already holding a copy — reseeding is what these weeks foretell, and a week that has had it is a card. Copies are known by what reseeding itself matches on: the title, within the team (Service.CarryOver's own dedup).
Nothing is projected without a calendar to project from: a per-sprint recurrence turns with the sprint, which is not a date. A process TURN is projected by its task's calendar instead (UpcomingTurns) — answering here as well would draw every one of them twice.
func UpcomingTurns ¶ added in v0.32.0
UpcomingTurns is every week, of the `weeks` beginning at `from`, in which a task comes due and whose OCCURRENCE has no turn yet.
An occurrence whose turn is already filed is left out: that turn is a card, and the board draws cards. What is left is what is coming — the turns nobody can act on yet but everybody has to plan around.
The question is asked of the occurrence and not of the week, because a turn can be MOVED inside its own cycle (CycleWindow). Asking about the week left a ghost standing where a moved turn came from — the same work drawn twice, once as a card where it now is and once as a projection where it was.
A task with no recurrence has no calendar and nothing is coming; a task whose process is paused files nothing, which the caller knows and this does not (it is given the task, not the process).
func ValidRecurrence ¶ added in v0.9.0
ValidRecurrence reports whether cycle is a storable recurrence value.
func WeeksCovered ¶ added in v0.32.0
WeeksCovered is every week a card occupies on the Triage board: the week it was placed in, through the week its end date reaches. Stretching a card over three weeks says the work takes three weeks, and each of them counts it against what the team can do — a stretched card is not one week of work filed early.
A card with no week covers none: it is in the strip, waiting for someone to say when. An end date inside (or before) its own week reaches nowhere.
func WithDomain ¶ added in v0.26.0
WithDomain carries the caller's choice of domain for a team, project or process being declared — asked only when more than one is writable. Cards never take one; a backend ignores the choice for them.
func Workable ¶
Complete reports whether a card counts as finished — an explicit done stage, or 100% readiness with no stage (done is derived) or on the recurrent stage (a finished recurrent card stays behind; Carry Over reseeds a fresh copy). A 100% card that is on review or locked is still unfinished, so it is NOT complete. Carry Over uses this so finished cards are not dragged forward. Workable reports whether a card can be picked up and worked on right now: it is neither finished nor parked awaiting someone else. It excludes done (complete) cards, cards on review (waiting on a reviewer), locked (blocked) ones and REFUSE ones (waiting on the lead's answer), keeping in-progress, not-yet-started and recurrent ones.
Types ¶
type Board ¶
type Board struct {
// Board names the board this snapshot came from — the name of its primary
// repository — so a backend can apply mutations against it. Empty on
// hand-built snapshots that never get persisted.
Board string `json:"board,omitempty"`
// Title/URL identify the board for display, mirroring the frontend Board.
Title string `json:"title,omitempty"`
URL string `json:"url,omitempty"`
Cards []Card `json:"cards"`
// SprintStates maps each team key ("" = the no-team group) to its pointer.
SprintStates map[string]SprintState `json:"sprintStates"`
// People is what the roster says about each login — the primary
// repository's users/<login>.yaml: a capacity a lead set, when one is.
// A login absent here is a person the board only knows from cards.
People map[string]Person `json:"people,omitempty"`
// TeamOrder lists the SprintStates keys in board order — the position of
// each team's hidden sprint-state card on the project. That position IS
// the team order every client shares (reordering teams moves the card).
TeamOrder []string `json:"teamOrder,omitempty"`
// Epics lists the Project board's columns in board order (the positions
// of the hidden epic-state cards that declare them).
Epics []EpicCol `json:"epics,omitempty"`
// Deadlines are the weeks carrying a deadline line, in board order.
Deadlines []Deadline `json:"deadlines,omitempty"`
// Processes lists the board's processes in board order, and Tasks the
// cards iterations are copied from (also in board order). Both are split
// out of Cards, like every other state card.
Processes []Process `json:"processes,omitempty"`
Tasks []Card `json:"tasks,omitempty"`
// Projects lists the project roster in board order (the positions of the
// hidden project-state cards) and ProjectStates maps each project to the
// card that declares it. A project groups epics: the Project board shows
// one project's epics as its columns.
Projects []string `json:"projects,omitempty"`
ProjectStates map[string]string `json:"projectStates,omitempty"`
// Primary names the repository a board's own entries belong to — the
// first of its domains. The store STAMPS every roster entry with its
// domain's name, the primary included, while a card that nothing
// places resolves to "" ("no repository decides this"); the two are
// different namespaces, and comparing them directly answered "another
// repository" for every card of the primary that nothing placed.
// Boards built by hand (fixtures, older callers) leave it empty, which
// is the same thing said the other way.
Primary string `json:"primary,omitempty"`
// Domains maps a roster entry's id — a team's sprint-state card, a
// project, an epic column, a deadline, a process, a task — to the domain
// (repository) it was declared in; cards carry their own Domain. Nil on
// a single-domain board, where nothing records one.
Domains map[string]string `json:"domains,omitempty"`
}
Board is an in-memory snapshot of a project board: its fields, the visible cards (sprint-state cards excluded) and the per-team sprint pointers. It mirrors the Board interface in web/src/providers/types.ts.
func MergeAsOf ¶ added in v0.30.0
MergeAsOf is one day on one screen with two moments in it: the teams the day is over for (past) contribute what they held THEN, everyone else what they hold NOW. It returns the merged board and the ids of the cards that came from the past — a record, which a client must not offer to change.
A card is judged by the copy that is CURRENT, whatever team the evening's copy names: a card moved between teams since, and a team renamed since, both name two different teams across the two boards, and asking each copy about its own team dropped the card from both halves — it stood on the board that evening and vanished from the record of it.
func NewBoard ¶
NewBoard builds a Board snapshot from the full card list, splitting the hidden sprint-state cards out of Cards into SprintStates (keyed by team, "" = the no-team group). It mirrors mapProject's split: a sprint-state card's Team is its key, its SprintStart is the team's current sprint and its StartDate (the "Start" field) is the previous sprint.
func NewBoardIn ¶ added in v0.29.0
NewBoardIn is NewBoard for a board whose entries are STAMPED with their domain's name — which is every board the store hands over, the primary included. The assembly's own rules ask "the same repository?" while it runs (declaredMirrors), so the primary's name has to arrive with the cards rather than be set on the result afterwards.
func Visible ¶ added in v0.26.0
Visible is the board as a visitor who can read only some domains sees it. Cards, teams, projects, columns, deadlines, processes and tasks of an unreadable domain are absent. A card whose team is unreadable stays, under its team name — the team's order slot and sprint pointer do not. An entry with no recorded domain belongs to the primary. The input is not modified.
type Capacity ¶ added in v0.32.0
type Capacity struct {
Points int `json:"points,omitempty"`
}
Capacity is a team's throughput: the POINTS a week it gets through, which is what a week of its plan is weighed against. Never derived — 0 means nobody has said (PointsAWeekOf).
It carried three more numbers once: a limit in CARDS a week, derived from what the team had been finishing, and the shares of it that client and internal work might take. Nothing ever wrote them — not one team file on the production board had the block — and no client read them back: the bar they were drawn for is long gone from the boards. A derived number nobody looks at is a rule that can only rot.
type Card ¶
type Card struct {
ItemID string `json:"itemId"`
Title string `json:"title"`
// Assignees are logins; Author is the login that created the card.
Assignees []string `json:"assignees"`
Author string `json:"author,omitempty"`
// Team is the card's team label ("" = the no-team group).
Team string `json:"team,omitempty"`
Zone ZoneKey `json:"zone,omitempty"`
// Size is what somebody said the card weighs — S, M, L or XL — set on a
// daily sync or by a sizing tool; "" is unsized. The points a board sums
// are derived from it (Points, PointsOf), never stored.
Size SizeKey `json:"size,omitempty"`
// Progress is the readiness percentage (0..100); 0 also stands for unset,
// matching the frontend's `progress ?? 0`.
Progress int `json:"progress"`
Stage StageKey `json:"stage,omitempty"`
// Day is the ISO date (yyyy-mm-dd) the card is planned to finish/be due on.
Day string `json:"day,omitempty"`
// StartDate is the day the card starts on; SprintStart is the start day of the
// sprint it belongs to (what the day boards orient by).
StartDate string `json:"startDate,omitempty"`
SprintStart string `json:"sprintStart,omitempty"`
// Week is the week the card is scheduled for, a Monday: the row it stands
// in on the Triage board. A Week AHEAD of the current one is a backlog
// placement — the card is on no day board until that Monday (B1).
Week string `json:"week,omitempty"`
// Parked says the card is on its team's SHELF rather than in the plan.
// EXCLUSIVE with Week — a card is on the shelf or in a week, never both,
// or the board draws the same work twice and counts it twice against the
// week. Read it through board.InBacklog.
Parked bool `json:"parked,omitempty"`
// Epic names the Project-board column this card belongs to ("" = none). An
// epic card's row is its Week; StartDate..Day span the weeks its slot
// covers when it stretches over more than one.
Epic string `json:"epic,omitempty"`
// Process, on the two process state cards, is the process's name (on its
// own card) or the process a task belongs to. On an ordinary card it
// is empty — an iteration points at its task instead.
Process string `json:"process,omitempty"`
// Task, on an iteration, is the item id of the process task it
// was spawned from ("" = not an iteration). The link is what makes the
// process's history: its iterations are the cards that name it.
Task string `json:"task,omitempty"`
// Paused, on a process state card, stops it spawning: the tasks and
// their history stay exactly as they are, and nothing new is filed until
// it is resumed. Work that stops for a month is not work that was
// deleted, and a process nobody can pause gets deleted instead.
Paused bool `json:"paused,omitempty"`
// Accumulate, on a task, spawns an iteration on every due date even
// while the previous one is still open — unpaid months pile up as
// separate cards. Off (the default), an open iteration simply goes
// overdue and the next one waits for it.
Accumulate bool `json:"accumulate,omitempty"`
// Project is the project side of the card's column. A column is
// identified by the PAIR (project, epic), because epic names are unique
// only within a project — "Docs" or "Auth" belong to every project — so a
// card filed under a column must name both. On a project-state card it is
// the project's own name; on an epic-state card, the project owning it.
Project string `json:"project,omitempty"`
// ReviewOf, on a review card, is the itemId of the original card it reviews.
ReviewOf string `json:"reviewOf,omitempty"`
// Parent, on a subtask, is the itemId of the card it belongs to ("" = a
// top-level card). Subtasks are one level deep: a parent cannot itself be
// a subtask, and a card with subtasks cannot become one.
Parent string `json:"parent,omitempty"`
// Recurrence is a recurrent card's reseed cycle: "" = every sprint (the
// default), "week" / "month" = only once that interval has elapsed since
// the sprint the card is bound to (see RecurrenceDue).
Recurrence string `json:"recurrence,omitempty"`
// ReviewRound counts a review card's review rounds. A card sent back for
// another review after already passing starts round 2, 3, … The first
// review is implicit and left at 0 (round 1 is not shown).
ReviewRound int `json:"reviewRound,omitempty"`
CreatedAt string `json:"createdAt,omitempty"`
// Rank is the card's ordering key within its list (see RankBetween): a
// plain string compared bytewise, so a reorder rewrites this card alone.
Rank string `json:"rank,omitempty"`
// DoneFrom is the progress the card had when a write took it to 100; a
// reopen restores it. Stored, not derived from history, because an
// action must never depend on a log that may be cut at a horizon.
DoneFrom int `json:"doneFrom,omitempty"`
// DoneAt is the board day (yyyy-mm-dd) a write took the card to 100,
// cleared when it drops below again — what lets a board show a card the
// day it was done and drop it the next without reading history.
DoneAt string `json:"doneAt,omitempty"`
// LeftAt is the board day (yyyy-mm-dd) the × took the card off.
//
// On a PERSONAL card it is a live rule: the board shows it that day and
// before, off it from the next, and re-dating the card (calendar, defer)
// clears it — the card is on a day again.
//
// On a TEAM card the × demotes into the previous sprint instead, dates
// and all, and this records the day that happened. Nothing live reads it
// there: today's board drops the card, which is what the × is for. It is
// what a RECORD of that day gives back (G60) — the card was worked and
// finished on it — and a fact about a day gone by is not undone by
// re-dating the card, so it stands.
LeftAt string `json:"leftAt,omitempty"`
// Link is a URL the card points at — the only trace an issue-backed
// card keeps of its issue. Nothing is fetched through it.
Link string `json:"link,omitempty"`
// GitHubID is the Projects v2 item id a migrated card came from, kept so
// the id can still be resolved for a while; empty on cards born later.
GitHubID string `json:"githubId,omitempty"`
// Mirrors are additional Project-board columns the card stands in — the
// same card, one file and one log, shown in more than one project. The
// (Project, Epic) pair above stays its home: DomainOf reads it, mirrors
// never move the card. See mirrors.go.
Mirrors []Placement `json:"mirrors,omitempty"`
// Domain names the repository the card lives in ("" = the primary or a
// single-domain board). MovedFrom/MovedAt record a cross-domain move,
// so a torn move resolves from the tree alone.
Domain string `json:"domain,omitempty"`
MovedFrom string `json:"movedFrom,omitempty"`
MovedAt string `json:"movedAt,omitempty"`
// Description is the card's free-form details (a draft body minus its appended
// action log, or an issue/PR body). Notes are the card's dated work notes.
Description string `json:"description,omitempty"`
Notes []Note `json:"notes,omitempty"`
}
Card is a single board item with the well-known field values the boards orient by. It mirrors the Card interface in web/src/providers/types.ts.
func BacklogOrder ¶ added in v0.34.0
BacklogOrder sorts a shelf's cards the way it is read: the order somebody arranged by hand first, and where nobody has, the oldest first.
Both halves are wanted. Dragging is how a backlog gets prioritised, so a card placed by hand must stay where it was put. But a shelf nobody has sorted should not read as random: the card that has waited longest is the one being asked about, so it goes to the top and the shelf answers "what has been sitting here" without anybody arranging it.
func Followers ¶ added in v0.29.0
Followers lists the cards whose FILE moves with this one: its subtasks and its review card, and THEIRS — the store's cascade recurses, so a walk that stopped at the first ring went blind exactly where a columnless child hides a columned review card. What follows a card cannot be left behind by a move: neither its file nor the rules that read it. Cost: one pass over the board's cards per ring of the walk, and the rings are shallow (a card, its subtasks and its review card). A caller that asks for every card of a COLUMN — SetEpicProject does — pays that per card; at board scale it is nothing, and a card index would be the answer if a board ever made it one.
func Iterations ¶ added in v0.18.0
Iterations lists the cards spawned from a task, in board order.
func MeView ¶
MeView returns the cards on the personal day board for a user on a given day: the user's cards (user = "" means everyone) that belong to the sprint that was active on the viewed day (activeSprint) and whose scheduled day has arrived (startDate empty or on or before the viewed day). Today shows the current sprint; rolling back into the previous sprint's days shows that sprint's cards. A card whose team had no active sprint on the day, or that is deferred to the future, never shows. It mirrors myCards in MeBoard.tsx.
func PersonalReseed ¶ added in v0.26.0
PersonalReseed lists the finished recurrent cards of login's personal board whose next iteration is due on day — the cards a reader of the board seeds fresh copies of. A personal board has no carry-over, so the calendar turns its cycles: the per-sprint default means every day there (the copy is due the day after the card was finished), and a week/month cycle counts from the card's start day, as the team rule counts from its sprint. Never twice in one day — a card finished today, however late, rests until tomorrow — and never once a fresh copy (same title, later start) exists, so reading the board again reseeds nothing. A finished card without a day on record is never due: there is nothing to count from.
func PersonalView ¶ added in v0.26.0
PersonalView is a person's personal repository seen as a backlog: every open card in it, in board order, plus the cards finished on `day` — a done card is seen the day it was done and is gone the next morning, since no carry-over sweeps a personal board. A done card without a doneAt (finished before the field existed) is treated as done on an earlier day.
func TeamGrid ¶
TeamGrid returns the cards on the Team board for one team on a given day: what the people are WORKING ON then, and nothing else. It is the Triage board's own "now" column for that day, less anything put off to later, and it mirrors filteredCards in TeamBoard.tsx.
A card is in hand on a day when it is this team's, is not a subtask (those ride with their parent), is not parked on a list, somebody has said WHEN it is for (a week, a date or a sprint — a card with none of the three is the Triage strip, which is an inbox rather than a day's work), has not been planned into a week AFTER that day's (B1: a card placed in a week ahead is on no day board until its Monday), has not been deferred past that day, and is either open or was finished on that very day — a card finished yesterday belongs to yesterday, and a board that keeps it is a board nobody can read.
One day answers differently, and deliberately: the day a SPRINT BEGAN is the whole sprint. A lead opens it every few mornings and goes through it with the team, so it holds the work the sprint opened with, the work typed into it since, and the work already closed — not only what is still in hand. What still leaves that day is what was taken OUT of the sprint: work deferred past today, and work planned into a week still to come.
That is the whole rule. It used to be seven, layered: the week's own work, the sprint's start day, the card's own scheduled day, the range between its dates, the days of sprints it had passed through, and two special cases for deferral. Every one of them was a different reason for a card to be drawn, and between them they managed both halves of being wrong — work nobody was doing appeared on the day, while a card scheduled for last Tuesday and never finished fell off the board entirely, since no rule reached it any more. A day board answers one question, so it asks one.
Looking BACK is not this function's job: a day already past is answered from the board's history, as it was (the snapshot path). Looking forward works here, because every gate it applies is relative to the day asked about — a card deferred to Thursday is in hand on Thursday.
func Unreachable ¶ added in v0.31.0
Unreachable is Reachable's complement over the board's own cards, in board order: the cards no view shows.
type CreateInput ¶
type CreateInput struct {
// ItemID, when set, is the id the card is created WITH — a backend that
// mints its own ids (the git store) honours it, so the cache can hand out
// the final id before the write lands; a backend that cannot ignores it.
ItemID string `json:"itemId,omitempty"`
// Domain, on a roster stub (a team, project or process state card), is
// the repository to declare it in; "" is the primary. Cards never carry
// one — their domain is inherited (see DomainOf).
Domain string `json:"domain,omitempty"`
// Personal files the card in the caller's personal domain (Domain names
// it) instead of where the home rule would put it; such a card stays
// there whatever team or project it is later given.
Personal bool `json:"personal,omitempty"`
Title string `json:"title"`
Zone ZoneKey `json:"zone,omitempty"`
Size SizeKey `json:"size,omitempty"`
Day string `json:"day,omitempty"`
Start string `json:"start,omitempty"`
SprintStart string `json:"sprintStart,omitempty"`
Assignee string `json:"assignee,omitempty"`
Team string `json:"team,omitempty"`
ReviewOf string `json:"reviewOf,omitempty"`
Parent string `json:"parent,omitempty"`
Week string `json:"week,omitempty"`
// Parked puts the card straight on its team's SHELF: born there rather
// than born in the strip and moved, which is one commit instead of two
// and spares every reader the instant in between.
Parked bool `json:"parked,omitempty"`
Epic string `json:"epic,omitempty"`
Project string `json:"project,omitempty"`
Process string `json:"process,omitempty"`
Task string `json:"task,omitempty"`
Recurrence string `json:"recurrence,omitempty"`
Paused bool `json:"paused,omitempty"`
// Body is the draft's description, written with the create rather than
// after it: a card that appears without its text, then fills in a second
// later, reads as a card the board got wrong.
Body string `json:"body,omitempty"`
}
CreateInput is the payload for creating a card on a board: the fields a create can set. It mirrors NewCardInput in web/src/providers/types.ts. It lives in the board package (not boardservice) so a backend can implement the create method without importing boardservice. An empty field is left unset.
type Deadline ¶ added in v0.17.0
type Deadline struct {
Week string `json:"week"`
Project string `json:"project,omitempty"`
ItemID string `json:"itemId,omitempty"`
}
Deadline is a date marked on the Project board: the week its line sits on, the project it belongs to ("" = none), and the hidden card that declares it. A project holds at most one deadline per week.
type DomainResolver ¶ added in v0.26.0
type DomainResolver interface {
// CardDomain is the domain of another card by id.
CardDomain(id string) (string, bool)
// ProjectDomain is the domain a project was declared in.
ProjectDomain(name string) (string, bool)
// TeamDomain is the domain a team was declared in.
TeamDomain(name string) (string, bool)
}
DomainResolver answers where the things a card refers to live. A false ok means the reference is unknown and does not decide.
func Resolver ¶ added in v0.26.0
func Resolver(b Board, primary string) DomainResolver
Resolver is a DomainResolver over a board snapshot, for deciding where a card WOULD live after a change — the write check of a move needs the destination before anything is written.
type EpicCol ¶ added in v0.17.0
type EpicCol struct {
Name string `json:"name"`
Project string `json:"project,omitempty"`
ItemID string `json:"itemId,omitempty"`
}
EpicCol is one column of the Project board: its name, the project that owns it, and the hidden epic-state card that declares it. The (Project, Name) pair is the column's identity — epic names repeat across projects, so a name alone does not identify a column.
type Event ¶
type Event struct {
// ID anchors the event like a note id (itemID:line or comment-derived).
ID string `json:"id"`
// Kind is one of the Event* constants below.
Kind string `json:"kind"`
// Actor is the GitHub login that made the change ("" when unknown).
Actor string `json:"actor,omitempty"`
// From and To carry the change's old and new value, kind-specific.
From string `json:"from,omitempty"`
To string `json:"to,omitempty"`
// At is the RFC3339 timestamp the change was recorded.
At string `json:"at"`
}
Event is one recorded action on a card: who changed what, when. Events live in the card itself — a draft card keeps them as machine lines in its body log next to the work notes, an issue/PR card in a dedicated log comment — so the history is stored with the card and dies with it. Writes that bypass aeman (edits in the GitHub Projects UI) leave no events: Projects v2 exposes no field history, so aeman records changes at its own write path.
func ParseEventBody ¶
ParseEventBody parses a log-line body produced by FormatEventBody. ok is false when the body is a plain work note, not an event line.
type Link ¶
type Link struct {
URL string `json:"url"`
// Kind is "issue", "pull" or "link".
Kind string `json:"kind"`
Owner string `json:"owner,omitempty"`
Repo string `json:"repo,omitempty"`
Number int `json:"number,omitempty"`
// Title is the issue/PR title, when resolved.
Title string `json:"title,omitempty"`
// State is the issue/PR state (open/closed/merged), when resolved.
State string `json:"state,omitempty"`
}
Link is one URL found in a card's description. GitHub issue and pull-request links are recognised and addressable (Owner/Repo/Number); everything else is a plain link. Title and State are filled by a LinkResolver, not here.
func ExtractLinks ¶
ExtractLinks finds every link in a free-form description — full URLs and GitHub owner/repo#number shorthands — classifies GitHub issue/PR references, dedupes (a shorthand and a full URL of the same item are one link), and orders the result the way the UI lists it: GitHub references first (in order of appearance), plain links after.
func ParseGitHubRef ¶
ParseGitHubRef parses a GitHub issue/PR reference — a full URL or the owner/repo#number shorthand. ok is false for anything else.
func (Link) FallbackTitle ¶
FallbackTitle is a readable card title for a GitHub ref whose real title could not be fetched (e.g. a token without access to a private repo): "Issue: owner/repo#123" or "Pull: owner/repo#123".
func (Link) IsGitHubRef ¶
IsGitHubRef reports whether the link addresses a GitHub issue or PR.
type Note ¶
type Note struct {
ID string `json:"id"`
Body string `json:"body"`
CreatedAt string `json:"createdAt"`
Author string `json:"author,omitempty"`
// Source is "comment" or "draft".
Source string `json:"source"`
}
Note is a dated work note attached to a card: an issue/PR comment, or a line stored in a draft issue's body when the card has no comment thread. It mirrors the Note interface in web/src/providers/types.ts.
type Person ¶ added in v0.37.0
type Person struct {
// Capacity is the roster's number — points a week — or 0 for "nobody
// has said" (see CapacityOfPerson).
Capacity int `json:"capacity,omitempty"`
}
Person is what the roster says about one login: for now, the points a week they get through, when a lead has set it (0 = nobody has said). It is the primary repository's users/<login>.yaml.
type Placement ¶ added in v0.28.0
Placement names a Project-board column: the (project, epic) pair. A column is the pair — epic names repeat across projects.
type Process ¶ added in v0.18.0
type Process struct {
Name string `json:"name"`
Project string `json:"project,omitempty"`
Paused bool `json:"paused,omitempty"`
ItemID string `json:"itemId,omitempty"`
}
Process is recurring work the team keeps doing: its name, the project it is part of, and the hidden card that declares it.
type SizeKey ¶ added in v0.37.0
type SizeKey string
SizeKey is what somebody said a card weighs: S, M, L or XL. It mirrors the SizeKey union in web/src/providers/types.ts; SizeNone ("") is unsized.
The LETTER is what is stored and what a person picks on a daily sync; the points are derived (Points), never written, so the scale can be retuned without touching a card. The scale doubles at each step so a week of S-work and a week of L-work can be compared at all: S is up to a couple of hours, M half a day to a day, L two to five days, XL a week or more.
const ( SizeNone SizeKey = "" SizeS SizeKey = "S" SizeM SizeKey = "M" SizeL SizeKey = "L" SizeXL SizeKey = "XL" )
The four sizes, mirroring web/src/size.ts.
func ParseSize ¶ added in v0.37.0
ParseSize reads a size as people and tools type it — any case, stray spaces — and refuses anything that is not one of the four: a size nothing knows would weigh nothing and read as unsized, silently. The empty string is SizeNone and is accepted: it is how a size is cleared.
type SprintState ¶
type SprintState struct {
Current string `json:"current"`
Previous string `json:"previous"`
ItemID string `json:"itemId"`
// Capacity is the team's, from its roster file; zero when nothing is set.
Capacity Capacity `json:"capacity,omitempty"`
}
SprintState is a team's explicit sprint pointer, read from its hidden sprint-state card: the current and previous sprint start dates (and the card's item id). It mirrors the SprintState interface in web/src/providers/types.ts; "" means the team has no such sprint yet.
type StageDef ¶
type StageDef struct {
Key StageKey `json:"key"`
Label string `json:"label"`
Color string `json:"color"`
}
StageDef describes a stage's label and progress-bar colour, mirroring StageDef.
type StageKey ¶
type StageKey string
StageKey is an explicit per-card status that recolours the progress bar. It mirrors the StageKey union in web/src/providers/types.ts and the stage model in web/src/stages.ts. StageNone ("") means the card carries no stored stage.
const ( StageNone StageKey = "" StageLocked StageKey = "locked" StageReview StageKey = "review" StageRecurrent StageKey = "recurrent" StageRefuse StageKey = "refuse" StageDone StageKey = "done" )
The explicit stages a card can carry, mirroring web/src/stages.ts. StageNone ("") is the absence of a stage (the implicit In Progress / not-started state).
func ApplyInProgress ¶
ApplyInProgress computes the (stage, progress) for moving a card to the implicit In Progress status: the stage is cleared and progress is nudged into the [10, 90] band only at its edges — under 10 becomes 10, a done or full (>=100) card drops to 90, otherwise the value is kept as-is (so a 91–99 value is left untouched, matching the frontend). It mirrors handleInProgress.
func ApplyProgress ¶
ApplyProgress computes the (stage, progress) resulting from setting a card's progress to raw. The value is first clamped for the current stage. Done is derived (no stage + 100%, see Complete), never stored: reaching 100% with no stage simply stays stage-less, and a legacy stored done clears itself when progress drops below full. review/locked stages are left as-is. It mirrors handleProgress.
func ApplyStage ¶
ApplyStage computes the (stage, progress) resulting from moving a card to the given stage. Done is derived, never stored: choosing it clears the stage and fills progress to 100% (Complete then reports the card finished). Choosing review or locked knocks a full (100%) card down to 90%, since those stages can never sit at full. Clearing the stage (StageNone) or any other case keeps progress. It mirrors handleStage.
func StageFromName ¶
StageFromName maps a Stage single-select option name onto a StageKey, mirroring stageFromName. An unknown (or empty) name yields StageNone.
type ZoneKey ¶
type ZoneKey string
ZoneKey is the colour zone a card belongs to, in the Ford sense. It mirrors the ZoneKey union in web/src/providers/types.ts ("" means no zone).
const ( ZoneNone ZoneKey = "" ZoneGray ZoneKey = "gray" ZoneGreen ZoneKey = "green" ZoneYellow ZoneKey = "yellow" ZoneRed ZoneKey = "red" )
The four Ford zones, mirroring web/src/zones.ts. ZoneNone ("") is no zone.
func ZoneOf ¶ added in v0.36.0
ZoneOf is the band a card is drawn in — its own zone, or PLANNED when it is in the working area and nobody said.
The working area is drawn as four bands and nothing else, so a card standing on a day has to be in one of them. Every client already decides that the same way (`card.zone ?? "gray"` in MeBoard and TeamBoard), which means the answer existed but only inside the clients: the API said nothing, and each reader — a script, an agent, a second client — had to reinvent the fallback or read the card as zone-less work in the middle of somebody's day.
Outside the working area the empty zone is kept, because there it is DRAWN: the backlog drawer gives a zone-less card a neutral line and the Triage grid no zone class at all. A card in the strip or on a shelf genuinely has no zone yet — the zones are statements about a DAY (critical means today, unplanned means it turned up today, "if time left" is a day's spare capacity), and a card on no day has not been the subject of one.