{{define "content" -}}
<style>
/* Scoped ready styles, inlined in the same flat todo.sr.ht idiom as the beads,
milestones and memory views (the scss bundle is not rebuilt in dev): square,
hairline borders, monospace ids, muted small type, no radius and no shadow.
Colours come from the CSS variables that mirror core.sr.ht's Bootstrap
palette in the light default and the prefers-color-scheme: dark variant
sourcehut ships. No JavaScript: the filters are a GET form and every
narrowing is a link. The card is .bead-row, the board's card, because it is
the same card read in a different arrangement. */
.ready {
--accent: #2f9e44;
--bd-bg: #ffffff; --bd-panel: #f2f3f5; --bd-fg: #212529;
--bd-muted: #6c757d; --bd-border: #ced4da; --bd-stripe: rgba(0,0,0,.05);
--bd-danger: #b52a2a;
}
@media (prefers-color-scheme: dark) {
.ready {
--bd-bg: #212529; --bd-panel: #343a40; --bd-fg: #dee2e6;
--bd-muted: #adb5bd; --bd-border: #6c757d; --bd-stripe: rgba(255,255,255,.05);
--bd-danger: #ff6b6b;
}
}
.ready { max-width: 52rem; }
.ready .ready-lede { color: var(--bd-muted); font-size: .82rem; margin: -.35rem 0 .8rem; }
/* filter bar: flat hairline controls, as on the board */
.ready-filter { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; margin-bottom: .4rem; }
.ready-filter input, .ready-filter select, .ready-filter button {
font: inherit; font-size: .82rem; padding: .25rem .45rem; color: var(--bd-fg);
background: var(--bd-bg); border: 1px solid var(--bd-border); border-radius: 0;
}
.ready-filter input[type="search"] { min-width: 12rem; flex: 1 1 12rem; }
.ready-filter button { background: var(--bd-panel); cursor: pointer; }
.ready-filter .ready-clear { font-size: .82rem; color: var(--bd-muted); }
/* the two things a reader must not over-read the page by: the ceiling, and the
databases that could not be read at all */
.ready-note { font-size: .78rem; color: var(--bd-muted); margin: 0 0 .4rem; }
.ready-note.capped { color: var(--bd-danger); }
/* one database's ready work: the lane head's shape, with the freshness line of
that database's own store under it */
.ready-group { margin-bottom: .9rem; }
.ready-head {
display: flex; align-items: baseline; gap: .5rem; padding: .3rem .5rem; font-weight: 700;
background: var(--bd-panel); color: var(--bd-fg);
border: 1px solid var(--bd-border); border-top: 2px solid var(--accent);
}
.ready-head a { color: var(--bd-fg); }
.ready-head .only { font-weight: 400; font-size: .75rem; color: var(--bd-muted); }
.ready-head .count { margin-left: auto; color: var(--bd-muted); font-weight: 400; font-size: .85rem; }
.ready-group .beads-freshness {
font-size: .78rem; color: var(--bd-muted); margin: 0;
padding: .15rem .5rem; border: 1px solid var(--bd-border); border-top: none;
}
.ready-group .beads-freshness a { color: var(--bd-muted); }
.ready-group .beads-freshness code { font-size: .72rem; color: inherit; }
.ready-body { border: 1px solid var(--bd-border); border-top: none; }
/* issue rows: the board's card, verbatim */
.bead-row { display: block; padding: .3rem .5rem; text-decoration: none; color: var(--bd-fg); border-top: 1px solid var(--bd-border); }
.bead-row:first-child { border-top: none; }
.bead-row:hover { background: var(--bd-stripe); text-decoration: none; color: var(--bd-fg); }
.bead-row .r-id { font-family: monospace; font-size: .72rem; color: var(--bd-muted); }
.bead-row .r-title { display: block; margin: .05rem 0 .2rem; color: var(--bd-fg); }
.bead-row .r-meta { display: flex; flex-wrap: wrap; gap: .25rem; align-items: center; }
.ready-dot { margin-right: .25rem; }
/* labels: sourcehut .label — squared, hairline-bordered, transparent */
.blabel {
display: inline-block; font-size: .7rem; line-height: 1.5; padding: 0 .3rem; white-space: nowrap;
border: 1px solid var(--bd-border); background: transparent; color: var(--bd-fg);
}
.blabel.prio { border-color: #c9930a; color: var(--bd-fg); font-weight: 700; }
.blabel.assignee { border-color: var(--accent); }
.blabel.muted { color: var(--bd-muted); }
.ready-empty { color: var(--bd-muted); font-style: italic; }
</style>
<div class="ready">
<h2>Ready to work</h2>
<p class="ready-lede">
{{.Data.Total}} ready {{if eq .Data.Total 1}}issue{{else}}issues{{end}}
across {{len .Data.Groups}} {{if eq (len .Data.Groups) 1}}database{{else}}databases{{end}}
— open, unblocked, and nobody is on {{if eq .Data.Total 1}}it{{else}}them{{end}} yet.
</p>
<form class="ready-filter" method="get" action="/ready">
{{/* The database selection is not a form control (narrowing to one is a link
on its header), but this form is a GET: without carrying it, searching
from one database would answer across all of them. */}}
{{range .Data.Filter.Databases}}<input type="hidden" name="db" value="{{.}}">{{end}}
<input type="search" name="q" value="{{.Data.Filter.Query}}" placeholder="Search ready work">
<select name="assignee">
<option value="">anyone</option>
{{$sel := .Data.Filter.Assignee}}
{{range .Data.Options.Assignees}}<option value="{{.}}"{{if eq . $sel}} selected{{end}}>@{{.}}</option>{{end}}
</select>
<select name="priority">
<option value="">any priority</option>
{{$psel := .Data.Filter.Priority}}
{{range .Data.Options.Priorities}}<option value="{{.}}"{{if eq . $psel}} selected{{end}}>P{{.}}</option>{{end}}
</select>
<button type="submit">Filter</button>
{{if .Data.Filter.Active}}<a class="ready-clear" href="/ready">Clear</a>{{end}}
</form>
{{if .Data.Filter.Databases}}
<p class="ready-note">
Narrowed to {{len .Data.Filter.Databases}} of the databases you can browse.
<a href="/ready{{withQuery .Data.Query "db" ""}}">Every database</a>
</p>
{{end}}
{{if .Data.Capped}}
{{/* A silent cap reads as "that is everything". */}}
<p class="ready-note capped">
More than {{.Data.Max}} databases matched; only the first {{.Data.Max}} were read.
Narrow with the database links below to see the rest.
</p>
{{end}}
{{if .Data.Failed}}
{{/* What went wrong is in the log, with the error. The reader gets the count,
because a page that hid the gap entirely would understate itself. */}}
<p class="ready-note">
{{len .Data.Failed}} {{if eq (len .Data.Failed) 1}}database{{else}}databases{{end}}
could not be read and {{if eq (len .Data.Failed) 1}}is{{else}}are{{end}} not counted here.
</p>
{{end}}
{{range .Data.Groups}}
<section class="ready-group">
<header class="ready-head">
<a href="/~{{.Database.OwnerName}}/{{.Database.Name}}/view/beads?ready=1">~{{.Database.Slug}}</a>
{{if ne (len $.Data.Filter.Databases) 1}}<a class="only" href="/ready{{withQuery $.Data.Query "db" .Database.Slug}}">only this one</a>{{end}}
<span class="count">{{len .Cards}} ready</span>
</header>
{{template "beadsHead" (dict "Repo" .Database "Ref" .Ref "Head" .Head)}}
<div class="ready-body">
{{$db := .Database}}
{{range .Cards}}
<a class="bead-row" href="/~{{$db.OwnerName}}/{{$db.Name}}/view/beads?issue={{.ID | urlquery}}">
<span class="r-id"><span class="ready-dot" title="Ready to work">⚡</span>{{.ID}}</span>
<span class="r-title">{{.Title}}</span>
<span class="r-meta">
{{if .PriorityLabel}}<span class="blabel prio">{{.PriorityLabel}}</span>{{end}}
{{if .Type}}<span class="blabel">{{.Type}}</span>{{end}}
{{if .Assignee}}<span class="blabel assignee">@{{.Assignee}}</span>{{end}}
{{range .Labels}}<span class="blabel muted">{{.}}</span>{{end}}
{{if .Blocks}}<span class="blabel muted">blocks {{.Blocks}}</span>{{end}}
</span>
</a>
{{end}}
</div>
</section>
{{else}}
{{if .Data.Filter.Active}}
<p class="ready-empty">Nothing ready matches these filters. <a href="/ready">Clear them</a>.</p>
{{else}}
<p class="ready-empty">Nothing is ready to work in the databases you can browse.</p>
{{end}}
{{end}}
</div>
{{- end}}