Documentation
¶
Overview ¶
templ: version: v0.3.1020
templ: version: v0.3.1020
Package webui serves a read-only, live-updating web dashboard over the task queue: a pure projection consumer of the store's facts and task views (ADR-0003). One journal tailer fans out coalesced change notifications; each connected browser receives server-rendered HTML fragments over SSE and swaps them into the page by container id.
The package cannot mutate the queue: the worst failure is a stale dashboard, never journal corruption.
Index ¶
- Constants
- Variables
- func Board(data DashboardData) templ.Component
- func DeadLetterTable(data DashboardData) templ.Component
- func FactFeed(data DashboardData) templ.Component
- func FilterBar(data DashboardData) templ.Component
- func MetricsRow(data DashboardData) templ.Component
- func Page(data DashboardData) templ.Component
- func StatusCards(data DashboardData) templ.Component
- func TaskDetail(data DashboardData, t task.Task, facts []journalFactView) templ.Component
- func TaskDetailPage(data DashboardData, detail taskDetailData) templ.Component
- func TaskNotFoundPage(id string) templ.Component
- func TaskTable(data DashboardData) templ.Component
- type BoardColumn
- type BudgetView
- type Config
- type DashboardData
- type FilterState
- type Hub
- type ProjectSummary
- type Server
Constants ¶
const ( DefaultAddr = "127.0.0.1:8090" DefaultPoll = 500 * time.Millisecond DefaultHeartbeat = 15 * time.Second // BannerPrefix and BannerReadOnlySuffix define the serve startup // banner's exact shape — shared by the CLI print and the e2e/smoke // parsers, which nearly broke twice over string drift (00:21 e3). BannerPrefix = "tq: dashboard on http://" BannerReadOnlySuffix = " (read-only)" BannerWritesSuffix = " (writes ENABLED: cancel/rescue from the UI)" )
Default configuration values.
Variables ¶
var ErrTokenRequiredOnLAN = errors.New(
"webui: refusing to serve on non-loopback addr without a token " +
"(the dashboard exposes task payloads and error tails; " +
"bind 127.0.0.1 or pass --auth-token / $TQ_SERVE_TOKEN)",
)
ErrTokenRequiredOnLAN is returned by Config.Validate and Server.Run when the address binds beyond the loopback interface without an AuthToken. The dashboard is read-only by construction (ADR-0003) but renders every task payload and error tail, so non-loopback binds are default-deny.
Functions ¶
func Board ¶
func Board(data DashboardData) templ.Component
Board renders the kanban projection (inner content of #frag-table when the filter selects the board): one column per lifecycle status — the queue's state machine made visible, cards newest first. Columns are read-only projections, not drag-and-drop lanes: moving a card is queue mutation and waits for the --allow-writes gate (ADR-0003 Phase D).
func DeadLetterTable ¶
func DeadLetterTable(data DashboardData) templ.Component
DeadLetterTable renders the dead-letter queue (inner content of #frag-dlq).
func FactFeed ¶
func FactFeed(data DashboardData) templ.Component
FactFeed renders the live fact tail (inner content of #frag-feed): the journal itself, terminal-style. Newest facts land at the bottom. Each line's task id links to the detail page — a thin custom variant of the library Scrollback (which renders plain text lines only); the classes mirror its markup so the visual language stays one.
func FilterBar ¶
func FilterBar(data DashboardData) templ.Component
FilterBar renders the search/filter form (inner content of #frag-filters).
func MetricsRow ¶
func MetricsRow(data DashboardData) templ.Component
MetricsRow renders the overview's two pure-SVG charts (M19): the fact rate sparkline (facts per 5-minute slice of the last hour) and the completion histogram (time-to-complete: queue wait + run, honestly labeled). Both are server-rendered SVG — no JavaScript, CSP-safe.
func Page ¶
func Page(data DashboardData) templ.Component
Page is the full dashboard page (ADR-0003): a live read-only projection of the journal. Fragments are server-rendered into the #frag-* containers so the page works without JS; SSE then replaces innerHTML.
func StatusCards ¶
func StatusCards(data DashboardData) templ.Component
StatusCards renders the overview stat band (inner content of #frag-stats). The four active states carry tone icons and link to their filtered view; cancelled and total stay quiet (no icon). Marker classes (card-pending, …) are kept as stable hooks for tests and CSS.
func TaskDetail ¶
func TaskDetail(data DashboardData, t task.Task, facts []journalFactView) templ.Component
TaskDetail renders the per-task detail page body. The two live regions sit in #frag-detail / #frag-timeline containers so the /task/{id}/events stream can swap them over SSE exactly like the dashboard fragments. The payload section sits BETWEEN them, deliberately static: a payload is immutable after enqueue, so an open prompt or raw pane survives every live swap, and the SSE stream only re-renders what can actually change.
func TaskDetailPage ¶
func TaskDetailPage(data DashboardData, detail taskDetailData) templ.Component
TaskDetailPage is the per-task detail page.
func TaskNotFoundPage ¶
TaskNotFoundPage is the styled 404 for /task/{id}: an unknown id keeps the dashboard chrome and a way back instead of a bare error page.
func TaskTable ¶
func TaskTable(data DashboardData) templ.Component
TaskTable renders the live task table (inner content of #frag-table).
Types ¶
type BoardColumn ¶
BoardColumn is one lifecycle column of the board view: the true count of tasks in this status (under the filter's project/query scope) plus the newest boardColumnLimit cards. Truncated is how many older tasks the column omits (the "+N older" link opens them in the table view).
type BudgetView ¶
BudgetView is the daily agent-spend projection: enqueued today vs the operator-set cap. Nil in the snapshot when no cap is configured.
func (BudgetView) Tone ¶
func (b BudgetView) Tone() display.StatTone
Tone picks the card's semantic color: green under 75%, amber under the cap, red at/over it.
type Config ¶
type Config struct {
// Addr is the TCP address to bind. Default 127.0.0.1:8090.
Addr string
// Poll is the journal tail interval. Default 500ms.
Poll time.Duration
// Heartbeat is the SSE keepalive interval. Default 15s.
Heartbeat time.Duration
// RequestLog enables per-request access logging (method, path, status,
// duration) via slog at Info level. Off by default.
RequestLog bool
// DailyBudget mirrors the agent pool's --daily-budget so the dashboard
// can show the spend card (tasks enqueued today vs cap). 0 hides the
// card — the cap is an operator decision, not queue state.
DailyBudget int
// AuthToken, when set, requires every request (pages, API, SSE, static)
// to present the token via an Authorization: Bearer header or a `token`
// query parameter. Validate refuses non-loopback binds without it.
AuthToken string
// AllowWrites opts in to admin actions from the dashboard (cancel
// pending/running tasks, rescue dead-lettered ones). ADR-0003's default
// is a read-only projection; writes are a conscious operator decision
// guarded by per-form CSRF tokens (withCSRF) and, on non-loopback
// binds, by the auth token. Off by default.
AllowWrites bool
}
Config controls the dashboard server.
type DashboardData ¶
type DashboardData struct {
Counts map[task.Status]int
Total int
Tasks []task.Task
Dead []task.Task
Facts []journal.Fact
Filter FilterState
Now time.Time
Projects []ProjectSummary
Page int
TotalPages int
MatchTotal int
Budget *BudgetView
// Board holds the per-status columns when the filter selects the board
// view; nil on the table view (which uses Tasks/Page/TotalPages).
Board []BoardColumn
// JournalSeq is the journal watermark (highest fact seq) — the resume
// point every SSE/bridge consumer carries.
JournalSeq int64
// FactBuckets counts facts per equal slice of the last hour (the
// activity sparkline's series).
FactBuckets []float64
// CompleteMinutes is the time-to-complete (queue wait + run) of recent
// completed tasks in minutes, newest first (the completion histogram's
// raw data).
CompleteMinutes []float64
// Reviews holds the parsed verdict of every COMPLETED review task on
// the visible page (keyed by task id) — the agent-review loop made
// visible: an approve/request-changes badge in the table and findings
// on the detail page. Absent when the page shows no finished reviews.
Reviews map[string]executor.ReviewResult
// AllowWrites mirrors Config.AllowWrites: the templates render the
// admin affordances (cancel/stop/rescue forms) only when writes are
// enabled server-side.
AllowWrites bool
// Statuses holds the parsed outcome of every COMPLETED status task on
// the visible page (keyed by task id) — the done-prompt loop made
// visible: a report badge in the table and the report path + next-item
// count on the detail page. Absent when the page shows no finished
// status reports.
Statuses map[string]executor.StatusResult
}
DashboardData is the full projection snapshot one burst renders from.
type FilterState ¶
type FilterState struct {
Project string
Status task.Status
Query string
Page int
// Sort selects the task-table ordering: "", "age-asc", "age-desc",
// "priority-asc", "priority-desc", "attempts-asc", "attempts-desc".
// Empty keeps the severity order (dead, running, pending, ...).
Sort string
// View picks the task projection: viewTable (default) or viewBoard.
// Board drops Status (columns ARE the statuses) and ignores Sort/Page.
View string
// Band bounds the listing to one ADR-0015 band (backlog | hot |
// machine); empty means all bands.
Band string
}
FilterState is the URL-carried view filter (?project=&status=&q=). Page rides in the URL (?page=N) but is deliberately not part of QueryString: filter chips always reset to page 1.
func (FilterState) Empty ¶
func (f FilterState) Empty() bool
Empty reports whether no filter is active.
func (FilterState) QueryString ¶
func (f FilterState) QueryString() string
QueryString renders the filter as URL query parameters. Values are URL-escaped: a project or query containing &/=/space/% must round-trip through filterHref → parseFilter byte-identical (round-13 T5 armor).
type Hub ¶
type Hub struct {
// contains filtered or unexported fields
}
Hub fans out change notifications to all connected SSE clients. Notifications carry no payload — every one triggers a server-side re-render and a fresh full snapshot to each client (projection, not client state). The event ID is the journal watermark so browsers resume with a meaningful Last-Event-ID.
func (*Hub) ClientCount ¶
ClientCount returns the number of connected subscribers.
func (*Hub) Notify ¶
Notify broadcasts a change notification to every subscriber. seq is the journal watermark the notification represents.
func (*Hub) Unsubscribe ¶
Unsubscribe removes a subscriber channel.
type ProjectSummary ¶
ProjectSummary aggregates one project's live counts for the overview chips. Total is the project's task count across ALL statuses — the chip's headline number (R/P/D stay the operational breakdown).
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the read-only live dashboard. Create with New, then call Run.
func New ¶
New creates a Server over the given store. The store is read-only from this package's perspective: only Get, List and Facts are called.
func (*Server) Handler ¶
Handler returns the dashboard's HTTP routes:
GET / dashboard page
GET /project/{name} dashboard pinned to one project
GET /task/{id} per-task detail page
GET /task/{id}/events per-task SSE stream (detail fragments + resume)
GET /api/events SSE stream (dashboard fragments + resume)
GET /api/stats JSON status counts
GET /api/facts JSON journal cursor (?after=SEQ&limit=N)
With Config.AllowWrites the admin routes are added on top:
POST /task/{id}/cancel withdraw a pending task / request a running stop
POST /task/{id}/rescue re-queue a dead-lettered task (fresh attempts)
Every response carries strict security headers (securityHeaders); the default dashboard is read-only by construction, writes are opt-in and CSRF-guarded (withCSRF).