plugin

package module
v0.6.0 Latest Latest
Warning

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

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

Documentation

Overview

Package plugin is the hope plugin SDK: build a small JSON-RPC server that hope discovers across a fleet and renders in its UI. A plugin is your container, your language, your endpoint — hope dials in. Extend hope without joining it.

Minimal plugin:

p := plugin.New("badge-directory", "1.0.0").Icon("database")
p.View("counts", "Counts", plugin.KV, func(ctx context.Context) (any, error) {
	return map[string]any{"users": 1402301, "badges": 88123}, nil
})
log.Fatal(p.ListenAndServe(":8080")) // serves JSON-RPC 2.0 at /__hope

The container then declares the labels hope scans for:

hope.plugin=true
hope.plugin.port=8080
hope.plugin.path=/__hope

Index

Constants

View Source
const (
	ToneOK   = "ok"
	ToneWarn = "warn"
	ToneBad  = "bad"
	ToneInfo = "info"
)

Tone names a semantic color for a Badge (matches hope's ok/warn/bad/info).

View Source
const (
	StatusOK    = "ok"
	StatusInfo  = "info"
	StatusWarn  = "warn"
	StatusError = "error"
)

Advisory status levels for StatusReport.Level. hope colors the reported status by this and otherwise doesn't interpret the report.

View Source
const (
	ScopeEventsSubscribe = "events:subscribe" // receive hope events via OnEvent
	ScopeEventsPublish   = "events:publish"   // publish events onto hope's bus
	ScopeStorage         = "storage"          // durable per-install KV (p.Storage)
	ScopeSpecLabel       = "spec:label"       // add/update a service label in the plugin's own stack (p.Hope().AddServiceLabel)
)

Reverse-capability scopes a plugin may request. Plain strings so older/newer peers interoperate — an unknown scope is simply never granted. Additive: publish/storage/ action scopes land with their phases.

View Source
const ProtocolVersion = 1

ProtocolVersion is the plugin-protocol version this SDK speaks. hope sends its own; a mismatch degrades gracefully (unknown surfaces/kinds are skipped) rather than breaking, so a new plugin still works against an older hope and vice-versa.

Variables

View Source
var ErrNoReverseChannel = errors.New("hope reverse channel unavailable (no callback URL from hope.init)")

ErrNoReverseChannel is returned by Publish/Storage when hope hasn't provided a callback URL (an older hope, or one without [plugins] callback_url configured). The plugin still runs; it just can't call back into hope.

Functions

func Input

func Input(ctx context.Context) string

Input returns the "input" string from a Query view's params (the text the user typed into the query box), or "" if absent.

func NewError

func NewError(code int, msg string) error

NewError builds an *rpcError a handler can return to control the JSON-RPC error code sent to hope (e.g. invalid query -> codeInvalidArgs).

func Params

func Params(ctx context.Context, v any) error

Params unmarshals the current call's JSON-RPC params into v. Use inside an action/view/stream handler to read structured input.

func SearchQuery

func SearchQuery(ctx context.Context) string

SearchQuery reads the text a Search (autocomplete) view was called with (the "q" param). Empty when there's no query yet — return no items for that.

Types

type ActionDesc

type ActionDesc struct {
	Method string  `json:"method"`
	Label  string  `json:"label"`
	Icon   string  `json:"icon,omitempty"`
	Danger bool    `json:"danger,omitempty"`
	Fields []Field `json:"fields,omitempty"`
	// Steps turns the action's form into a WIZARD: hope renders each step's fields in
	// order with Back/Next/Finish and a stepper. Values accumulate across steps, so a
	// later step's cascading options / conditional fields read earlier answers. Set via
	// ActionSteps; leave Fields empty when using Steps. The action receives the merged
	// values from every step, exactly like a flat form.
	Steps []WizardStep `json:"steps,omitempty"`
	Tip   *Tooltip     `json:"tip,omitempty"` // hover tooltip on the action button (set with ActionTip)
	// Prefill seeds the form's initial values by field Key, merged OVER the invoking
	// context (the clicked row's columns + the page params) — so commanding from a row
	// pre-selects its target and an explicit Prefill can override or add to that. The
	// operator can still edit; the submitted value wins. Set with ActionPrefill.
	Prefill map[string]string `json:"prefill,omitempty"`
	// ValidateMethod names a Validate provider (registered with Plugin.Validate). hope
	// calls it with the current values as they change; it returns per-field errors that
	// render inline and disable Run until the form is valid. Set with ActionValidate.
	ValidateMethod string `json:"validateMethod,omitempty"`
	// ConfirmMethod names a Confirm provider (registered with Plugin.Confirm). After the
	// form (and any Danger gate), hope calls it with the entered values to compute a
	// go/no-go impact confirmation; the operator must approve before the action runs. Set
	// with ActionConfirm.
	ConfirmMethod string `json:"confirmMethod,omitempty"`
}

ActionDesc describes an invocable action (a mutation). Danger flags a destructive action so hope confirms before running it.

type ActionFunc

type ActionFunc func(ctx context.Context, in map[string]any) (any, error)

ActionFunc runs an action with the UI-collected field values.

type ActionOpt added in v0.0.3

type ActionOpt func(*ActionDesc)

ActionOpt configures a registered action beyond its label (its icon, …). Pass these to Action/DangerAction; the danger tone is set by DangerAction itself.

func ActionConfirm added in v0.5.0

func ActionConfirm(method string) ActionOpt

ActionConfirm points the action at a Confirm provider (registered with Plugin.Confirm) (#6). hope shows the computed impact confirmation after the form and runs the action only if the operator approves. Combine with DangerAction for a destructive-styled gate.

func ActionIcon added in v0.0.3

func ActionIcon(name string) ActionOpt

ActionIcon sets the action button's icon — a hope built-in icon name or one of this plugin's Icons keys. e.g. plugin.ActionIcon("rotate").

func ActionPrefill added in v0.5.0

func ActionPrefill(values map[string]string) ActionOpt

ActionPrefill seeds the form's initial values by field Key, merged OVER the invoking context (the clicked row's columns + the page params) (#3). Commanding from a row already prefills matching keys; use this to add or override. The operator can still edit.

func ActionSteps added in v0.5.0

func ActionSteps(steps ...WizardStep) ActionOpt

ActionSteps turns an action's form into a multi-step WIZARD (see ActionDesc.Steps). Pass nil for the action's flat fields when using steps. Values accumulate across steps — a later step's OptionsMethod/DependsOn reads the earlier answers via Params(ctx).

func ActionTip added in v0.0.4

func ActionTip(text string, pos ...TipPos) ActionOpt

ActionTip sets a hover tooltip on the action button explaining what it does, with an optional placement (see Tip). e.g. plugin.ActionTip("Refresh stats", plugin.TipBottom).

func ActionValidate added in v0.5.0

func ActionValidate(method string) ActionOpt

ActionValidate points the action at a Validate provider (registered with Plugin.Validate) (#4). hope calls it as the values change and disables Run while it returns field errors.

type Capabilities added in v0.0.6

type Capabilities struct {
	ViewKinds []string `json:"view_kinds"`
	Features  []string `json:"features"`
}

Capabilities is what the connected hope advertised: the supported view kinds (kv/table/…/component) and feature flags (static, empty, flyout, status). Read it with Caps and query it with Supports.

func Caps added in v0.0.6

func Caps(ctx context.Context) Capabilities

Caps returns the capabilities the connected hope advertised for the current call. Use it to adapt output across hope versions (see the package example above). Outside a hope-driven call (no headers) it returns an empty Capabilities.

func (Capabilities) Supports added in v0.0.6

func (c Capabilities) Supports(name string) bool

Supports reports whether hope advertised the named capability — a view kind ("component") or a feature ("static", "empty"). False when hope sent no capabilities (an older build), so guard optional output and keep a baseline fallback.

type Card

type Card struct {
	Title    string      `json:"title"`
	Subtitle string      `json:"subtitle,omitempty"`
	Icon     string      `json:"icon,omitempty"`
	Tone     string      `json:"tone,omitempty"` // ok|warn|bad|info accent
	To       string      `json:"to,omitempty"`
	Image    string      `json:"image,omitempty"` // absolute http(s) URL -> a hero image at the card top
	Fields   []CardField `json:"fields,omitempty"`
}

Card is one tile in a Cards view. Fields render as a small label/value list (the values may be rich cells — Badge/Number/…). A non-empty To makes the card navigate on click (plugin-relative, like a Link cell — see DetailLink).

type CardField

type CardField struct {
	Label string `json:"label"`
	Value any    `json:"value"`
}

CardField is one label/value line on a Card; Value may be a rich cell.

type CardsData

type CardsData struct {
	Items []Card `json:"items"`
}

CardsData is what a Cards view returns: a grid of cards.

type Cell added in v0.0.6

type Cell = map[string]any

Cell is a rich table/field cell — a typed shape hope renders specially (a pill, a link, a progress bar, …). It's a type *alias* for map[string]any, so it stays a plain JSON object on the wire and every builder below (and any hand-built map) satisfies it with no conversion; the name just documents intent where a cell is expected (e.g. a Comp's Cell field, or CCell). Build one with Badge/Link/Number/Time/Progress/Code/Image.

func Badge

func Badge(value, tone string) Cell

Badge renders value as a colored pill. tone is one of the Tone* constants ("" = neutral).

func Code

func Code(value string) Cell

Code renders value as inline monospace (an id, hash, snippet).

func DetailLink(value, pageID, arg string) Cell

DetailLink renders value as a link to one of this plugin's DetailPage ids, passing arg as the page's ParamKey — a master-detail link that needs no knowledge of the plugin's hope key. e.g. DetailLink("alice", "user", "42") -> the "user" detail page with param {<paramKey>: "42"}.

func ExternalLink(value, href string) Cell

ExternalLink renders value as a link that opens href in a new tab.

func Image

func Image(src, alt string, opts ...ImageOpt) Cell

Image renders src as an image (click opens the full image in a new tab). alt is the hover/accessible label. Unlike RPC calls, hope does NOT proxy image bytes — the browser loads src directly, so src MUST be an absolute http(s) URL reachable from the browser (e.g. a public on-demand webp/avif image proxy). A non-http(s) src renders as its alt text. Usable as a table cell or a stat/card/cards field value.

With no opts it's a small inline thumbnail. Control the render box with opts:

Image(u, alt)                      // default thumbnail
Image(u, alt, ImgW(240))           // 240px wide, height auto (keeps aspect)
Image(u, alt, ImgBox(110, 110))    // fixed 110×110 box, image centered, contained
Image(u, alt, ImgBox(110,110), ImgFit("cover")) // fill the box, cropping overflow
func Link(value, to string) Cell

Link renders value as an in-app link that navigates to a hope route `to` (e.g. a master-detail page). Use ExternalLink for an off-site URL.

func Number

func Number(n any, unit string) Cell

Number renders n right-formatted with thousands separators; unit ("" = none) is appended (e.g. "MB", "reqs").

func Progress

func Progress(frac float64) Cell

Progress renders frac (0..1) as a small progress bar.

func Time

func Time(unix int64) Cell

Time renders a unix timestamp (seconds or millis) as relative time ("2h ago"), with the absolute time on hover.

type ChartData

type ChartData struct {
	Type   string        `json:"type,omitempty"`
	Labels []string      `json:"labels"`
	Series []ChartSeries `json:"series"`
}

ChartData is what a Chart view returns: categorical labels on the x-axis and one or more named series of values. Type is "bar" (default) or "line". A line chart with one series and many points is a good time-series-at-rest view (use a stream for live). hope draws the axes, gridlines, legend, and scaling.

type ChartSeries

type ChartSeries struct {
	Name   string    `json:"name"`
	Values []float64 `json:"values"`
}

ChartSeries is one named line/bar series; Values aligns with ChartData.Labels.

type Comp added in v0.0.6

type Comp struct {
	Kind     CompKind   `json:"kind"`
	Children []*Comp    `json:"children,omitempty"` // containers
	Text     string     `json:"text,omitempty"`     // text/heading content
	Label    string     `json:"label,omitempty"`    // keyval label
	Cell     Cell       `json:"cell,omitempty"`     // cell primitive: a rich Cell
	Value    any        `json:"value,omitempty"`    // keyval value (a scalar or a rich Cell)
	Level    int        `json:"level,omitempty"`    // heading level 1..4
	Tone     string     `json:"tone,omitempty"`     // ok|warn|bad|info accent (text/heading/keyval/box)
	Icon     string     `json:"icon,omitempty"`     // icon primitive
	Values   []float64  `json:"values,omitempty"`   // sparkline points
	Gap      int        `json:"gap,omitempty"`      // container child gap, px
	Size     int        `json:"size,omitempty"`     // row/grid child weight | spacer height px
	Table    *TableData `json:"table,omitempty"`    // embedded table (CompTable)
	Tree     []TreeNode `json:"tree,omitempty"`     // embedded node tree (CTree) — folders + navigable leaves
	// Tip is an optional hover tooltip (an info icon) on a labeled node — a Heading, a
	// KeyVal (by its key), or a Card title. Set with .Help(text). Lets a plugin attach
	// explanatory help ANYWHERE it renders a custom surface, the same way table headers use
	// ColumnTips and fields use Help.
	Tip *Tooltip `json:"tip,omitempty"`
	// State is a timeline step's progress: "done" | "current" | "pending" (default pending) —
	// drives the dot fill and the row's emphasis. Set with .Done()/.Current()/.Pending().
	State string `json:"state,omitempty"`
}

Comp is one node in a Component tree. Build nodes with the constructors below (Box/Stack/CRow/CGrid for containers; Heading/CText/KeyVal/CIcon/Sparkline/CCell for terminals) rather than filling this struct by hand. Unknown kinds are skipped by hope, never fatal, so a newer primitive degrades gracefully on an older hope.

func Box added in v0.0.6

func Box(children ...*Comp) *Comp

Box builds a vertical container (a tile/card body) from children.

func CCard added in v0.5.0

func CCard(title string, body ...*Comp) *Comp

CCard builds a bordered card: a header (icon/title/subtitle + a tone stripe) over a body of child components — the good default "item" to render in a grid or a paged collection. C-prefixed (Card is already the data-card type). Chain .Ico/.Sub/.Toned for the header.

func CCell added in v0.0.6

func CCell(cell Cell) *Comp

CCell wraps any rich Cell (Badge/Link/Number/Time/Progress/Code/Image) as a standalone primitive, so the whole cell vocabulary works inside a Component tree.

func CGrid added in v0.0.6

func CGrid(children ...*Comp) *Comp

CGrid builds a responsive grid of children (see CRow on the C-prefix).

func CIcon added in v0.0.6

func CIcon(name string) *Comp

CIcon builds a single icon (a hope built-in name or one of this plugin's Icons keys). C-prefixed to leave room for future top-level icon helpers.

func CRow added in v0.0.6

func CRow(children ...*Comp) *Comp

CRow builds a horizontal container (children laid out left-to-right). Named CRow (not Row) because Row already builds a layout Node; a Comp tree uses CRow.

func CTable added in v0.0.10

func CTable(data *TableData) *Comp

CTable embeds a full table inside a Component tree — column headers, aligned cells, ellipsis, DetailLink/Image cells, all rendered by hope's table renderer. Build the data with plugin.Table(...). Ideal for a compact list-with-structure inside a flyout/panel (e.g. the badges on a canvas) instead of hand-stacking rows.

func CText added in v0.0.6

func CText(s string) *Comp

CText builds a line/paragraph of text (auto-escaped by hope). C-prefixed because Text is already a ViewKind constant.

func CTree added in v0.6.0

func CTree(nodes ...TreeNode) *Comp

CTree embeds a collapsible node tree inside a Component surface — folders with children and navigable leaves (a TreeNode with To navigates; To:"graph:<id>" selects a DAG in a graph sidebar). Ideal for a plugin-controlled navigator (the graph sidebar's DAG/folder browser).

func Divider added in v0.0.6

func Divider() *Comp

Divider builds a horizontal rule between sections of a tile.

func Heading added in v0.0.6

func Heading(text string, level int) *Comp

Heading builds a heading; level is clamped to 1..4 by hope (1 = largest).

func KeyVal added in v0.0.6

func KeyVal(label string, value any) *Comp

KeyVal builds a label + value line. value may be a plain scalar or a rich Cell (Badge/Number/…); the `any` mirrors Number's signature — hope renders a Cell specially and stringifies a scalar.

func Spacer added in v0.0.6

func Spacer(px int) *Comp

Spacer builds a vertical gap of px pixels.

func Sparkline added in v0.0.6

func Sparkline(vals ...float64) *Comp

Sparkline builds a tiny inline line chart from the given points.

func Stack added in v0.0.6

func Stack(children ...*Comp) *Comp

Stack builds a tight vertical container — good for a run of KeyVal lines.

func TStep added in v0.5.2

func TStep(label string, body ...*Comp) *Comp

TStep is one timeline row: Text is the label, and any body components render indented under it (in the content column, past the rail) — so a step can hold a detail box, a keyval, a table, whatever. Chain .Sub(detail), .At(value) for the right-aligned value (a timestamp, a Cell, any scalar), .Done()/.Current()/.Pending() for the state (dot fill + emphasis), and .Toned(tone) to override the dot/value color.

Custom states beyond done/current/pending are just a tone + an optional icon marker: the step reads as reached (emphasized) and the dot takes the tone color, or an icon replaces the dot entirely. The icon is a hope built-in name OR one of the plugin's OWN loaded SVGs (an Icons key), so a step's marker can be fully custom. e.g. a failure or a skip:

plugin.TStep("aborted").Ico("alert").Toned(plugin.ToneBad).At(plugin.Time(ts))
plugin.TStep("scanning").Ico("beaker").Toned(plugin.ToneInfo) // beaker = a plugin Icons key
plugin.TStep("skipped").Toned(plugin.ToneWarn)

func Timeline added in v0.5.2

func Timeline(steps ...*Comp) *Comp

Timeline builds a vertical timeline — a dot-and-connector rail of TStep rows, each with a label, optional detail, a right-aligned value, and a done/current/pending state. Ideal for a lifecycle / progress / audit trail (an order's queued -> uplinked -> completed).

plugin.Timeline(
    plugin.TStep("queued").Current().At(plugin.Time(ts)),
    plugin.TStep("uplinked").Pending(),
    plugin.TStep("tasked").Sub("executing over target").Pending(),
)

func (*Comp) At added in v0.5.2

func (c *Comp) At(value any) *Comp

At sets a timeline step's right-aligned value — a timestamp (plugin.Time), a rich Cell (Badge/Link), or any scalar. Empty renders a dim placeholder.

func (*Comp) Current added in v0.5.2

func (c *Comp) Current() *Comp

func (*Comp) Done added in v0.5.2

func (c *Comp) Done() *Comp

Done / Current / Pending set a timeline step's state (default pending).

func (*Comp) Gapped added in v0.0.6

func (c *Comp) Gapped(px int) *Comp

Gapped sets the gap (px) between a container's children.

func (*Comp) Help added in v0.5.1

func (c *Comp) Help(text string, pos ...TipPos) *Comp

Help attaches a hover tooltip (an info icon) to this node — works on Heading, KeyVal, and Card-title nodes. Chainable: plugin.Heading("Throughput", 4).Help("bytes/sec over the last minute"). Optional placement like Tip.

func (*Comp) Ico added in v0.5.0

func (c *Comp) Ico(name string) *Comp

Ico sets a card's (or any node's) leading icon — a hope built-in name or an Icons key.

func (*Comp) Pending added in v0.5.2

func (c *Comp) Pending() *Comp

func (*Comp) Sub added in v0.5.0

func (c *Comp) Sub(text string) *Comp

Sub sets a card's subtitle (the smaller line under its title).

func (*Comp) Toned added in v0.0.6

func (c *Comp) Toned(tone string) *Comp

Toned sets a semantic accent (ToneOK/Warn/Bad/Info) on a node — a colored heading, a tinted box border, a status-colored keyval.

func (*Comp) Weight added in v0.0.6

func (c *Comp) Weight(n int) *Comp

Weight sets a child's flex/grid weight inside a CRow/CGrid (0 = equal share), mirroring a layout Node's Weight.

type CompKind added in v0.0.6

type CompKind string

CompKind names one primitive in a Component tree. Containers hold Children; terminals carry their own inline payload (text, a cell, values, an icon).

const (
	CompBox       CompKind = "box"       // vertical container (a card/tile body)
	CompStack     CompKind = "stack"     // vertical container, tighter — labeled rows
	CompRow       CompKind = "row"       // horizontal container (children side by side)
	CompGrid      CompKind = "grid"      // responsive grid of children
	CompText      CompKind = "text"      // a line/paragraph of text
	CompHeading   CompKind = "heading"   // a heading (Level 1..4)
	CompDivider   CompKind = "divider"   // a horizontal rule
	CompSpacer    CompKind = "spacer"    // vertical gap of Size px
	CompKeyval    CompKind = "keyval"    // a Label + Value line (value may be a rich Cell)
	CompIcon      CompKind = "icon"      // a single icon (built-in name or an Icons key)
	CompSparkline CompKind = "sparkline" // a tiny inline line chart from Values
	CompCell      CompKind = "cell"      // any rich Cell (Badge/Link/Number/Time/Progress/Image)
	CompTable     CompKind = "table"     // an embedded table (rich cells, alignment, ellipsis)
	CompCard      CompKind = "card"      // a bordered card: header (icon/title/subtitle/tone stripe) + body children
	CompTimeline  CompKind = "timeline"  // a vertical timeline of TStep children (dots + connector + right value)
	CompTStep     CompKind = "tstep"     // one timeline step (Text label, Sub detail, .At value, state, tone)
	CompTree      CompKind = "tree"      // a collapsible node tree (folders); nodes navigate via TreeNode.To
)

type ConfirmFunc added in v0.5.0

type ConfirmFunc func(ctx context.Context) (ConfirmResult, error)

ConfirmFunc computes an impact confirmation from the entered values (Params) (#6). hope calls it after the form (and any Danger gate) and blocks the action until the operator approves the returned Message.

type ConfirmResult added in v0.5.0

type ConfirmResult struct {
	Title        string `json:"title,omitempty"`
	Message      string `json:"message"`
	Danger       bool   `json:"danger,omitempty"`
	ConfirmLabel string `json:"confirmLabel,omitempty"`
}

ConfirmResult is the go/no-go payload a Confirm provider returns (#6): hope shows Message (with an optional Title, Danger styling, and ConfirmLabel) and runs the action only if the operator approves. An empty Message skips the gate.

type Contribution

type Contribution struct {
	Surface Surface    `json:"surface"`
	Title   string     `json:"title,omitempty"` // tab/page title
	Icon    string     `json:"icon,omitempty"`
	Match   *Match     `json:"match,omitempty"`
	Pages   []PageItem `json:"pages,omitempty"`
	Node    *Node      `json:"node"`
	// Actions are method names of registered actions shown as a toolbar at the top of
	// this surface (page/panel/dashboard header) — page-level actions distinct from
	// leaf actions inside the layout. hope collects fields, confirms danger, audits.
	Actions []string `json:"actions,omitempty"`
	// ID is a stable address for a page contribution, so links can target it by name
	// (a plugin does NOT know its hope key or a page's positional path). A DetailPage
	// sets ID + ParamKey and is Hidden from the rail; a Link/DetailLink navigates to
	// it plugin-relative, and hope passes the URL arg as param[ParamKey].
	ID       string `json:"id,omitempty"`
	Hidden   bool   `json:"hidden,omitempty"`    // not listed in the rail (a link/detail target)
	ParamKey string `json:"param_key,omitempty"` // detail pages: the URL arg becomes param[ParamKey]
	// Subtitle sets the page header's sub/meta line (page surfaces) — e.g. a record
	// count or a connection string. {param} placeholders are filled from the page
	// param. Empty => hope shows the plugin name.
	Subtitle string `json:"subtitle,omitempty"`
	// Breadcrumbs render above the page heading (page surfaces). Each Crumb's Label
	// and To may contain {param} placeholders hope fills from the page param — e.g.
	// on a user detail page, [{Users, users}, {"user {id}"}].
	Breadcrumbs []Crumb `json:"breadcrumbs,omitempty"`
}

Contribution mounts one layout tree onto a Surface. For container/stack surfaces, Match decides which containers it applies to. For page surfaces, Pages (optional) turns one node into MANY rail entries that share the layout but each pass a distinct Param.

type Crumb

type Crumb struct {
	Label string `json:"label"`
	To    string `json:"to,omitempty"`
}

Crumb is one breadcrumb. To (optional) is a plugin-relative navigation target (like a Link cell / DetailLink); the last crumb is usually the current page and has no To. {param} placeholders in Label/To are filled from the page param.

type EmitFunc

type EmitFunc func(v any)

EmitFunc pushes one frame to a live stream.

type EmptyOpt added in v0.0.6

type EmptyOpt func(*EmptyState)

EmptyOpt configures a view's EmptyState (its icon, secondary text, or a custom Comp).

func EmptyComp added in v0.0.6

func EmptyComp(c *Comp) EmptyOpt

EmptyComp sets a fully custom empty state (a Comp tree — see component.go), overriding the icon/title/text — e.g. an icon plus a "create one" Link.

func EmptyIcon added in v0.0.6

func EmptyIcon(name string) EmptyOpt

EmptyIcon sets the empty state's leading icon (a built-in name or an Icons key).

func EmptyText added in v0.0.6

func EmptyText(s string) EmptyOpt

EmptyText sets the empty state's dim secondary line under the title.

type EmptyState added in v0.0.6

type EmptyState struct {
	Icon  string `json:"icon,omitempty"`  // leading icon (a built-in name or an Icons key)
	Title string `json:"title,omitempty"` // the headline, e.g. "No slow queries 🎉"
	Text  string `json:"text,omitempty"`  // a dim secondary line
	Comp  *Comp  `json:"comp,omitempty"`  // optional custom empty state (overrides Icon/Title/Text)
}

EmptyState is what hope shows when a view resolves to no data (an empty table, a tree with no nodes, a stat with no blocks) instead of the generic "empty" text. The plain Icon/Title/Text cover the common case; set Comp for a fully custom empty state (an icon + heading + a "create one" Link). Build it with plugin.EmptyView.

type Event added in v0.1.0

type Event struct {
	Seq     uint64          `json:"seq,omitempty"`
	Kind    string          `json:"kind"`
	Host    string          `json:"host,omitempty"`
	Project string          `json:"project,omitempty"`
	IDs     []string        `json:"ids,omitempty"`
	Source  string          `json:"source,omitempty"`
	Ts      int64           `json:"ts,omitempty"`
	Data    json.RawMessage `json:"data,omitempty"`
}

Event is one hope event delivered to an OnEvent handler. Mirrors hope's wire event; Data is kind-specific JSON (may be empty). Delivered only when the plugin holds the events:subscribe grant.

type EventFunc added in v0.1.0

type EventFunc func(ctx context.Context, e Event) error

EventFunc handles a hope event pushed to the plugin (one unary hope.event call per event). It should return quickly; hope treats a slow/erroring handler as a missed delivery and moves on. Requires the events:subscribe permission.

type Facet

type Facet struct {
	Key     string   `json:"key"`
	Label   string   `json:"label"`
	Options []Option `json:"options"`
}

Facet is one dropdown filter on a server table: a key, a label, and the choices.

type Field

type Field struct {
	Key         string    `json:"key"`
	Label       string    `json:"label"`
	Type        FieldType `json:"type,omitempty"` // one of the Field* consts (default FieldText)
	Placeholder string    `json:"placeholder,omitempty"`
	Hint        string    `json:"hint,omitempty"` // a dim line UNDER the field (always visible)
	// Help is a longer explanation shown in a tooltip off a small info icon next to the
	// label — for detail that would clutter the form as an always-visible Hint. Both may
	// be set: Hint for the one-liner, Help for the "what is this / why" on hover.
	Help     string   `json:"help,omitempty"`
	Value    string   `json:"value,omitempty"`
	Optional bool     `json:"optional,omitempty"`
	Options  []Option `json:"options,omitempty"`
	// Number-field bounds/step/unit (Type:"number"). Unit renders as a suffix on the input.
	Min  float64 `json:"min,omitempty"`
	Max  float64 `json:"max,omitempty"`
	Step float64 `json:"step,omitempty"`
	Unit string  `json:"unit,omitempty"`
	// AllowCustom, on a Type:"combobox" field, lets the operator commit a typed value that
	// isn't in Options — a freeform entry with suggestions. Without it a combobox is a
	// type-ahead select restricted to the list. Ignored by other field types.
	AllowCustom bool `json:"allowCustom,omitempty"`
	// OptionsMethod names an Options provider (registered with Plugin.Options); when
	// set, hope fetches this select's choices from that method live as it renders the
	// form, instead of using the static Options above. The JSON key is camelCase to
	// match hope's PromptField, which the fields map onto directly.
	OptionsMethod string `json:"optionsMethod,omitempty"`
	// RefreshEvery, with OptionsMethod, re-fetches this select's options every N seconds
	// while the form is open — so a live label ("in contact / queues next pass", a changing
	// count) stays current without closing the modal. 0 (default) fetches once. camelCase to
	// match hope's PromptField.
	RefreshEvery int `json:"refreshEvery,omitempty"`
	// ResolveMethod names a Resolve provider (registered with Plugin.Resolve). When the
	// form's values change, hope calls it with the current values and renders the
	// returned component node inline below the fields — a selection can drive a live
	// preview, confirmation, or result. camelCase for the same PromptField reason.
	ResolveMethod string `json:"resolveMethod,omitempty"`
	// Fields is the per-item sub-schema of a Type:"group" field — a repeatable
	// array-of-objects the operator adds/removes rows of (a forms-builder). The action
	// receives the group's value as a []map[string]any, one map per row. AddLabel names
	// the "+ add" button.
	Fields   []Field `json:"fields,omitempty"`
	AddLabel string  `json:"addLabel,omitempty"`
	// DependsOn conditionally shows this field: it renders ONLY when the field named by
	// DependsOn currently equals DependsValue — or, when DependsValue is empty, when that
	// field is non-empty/truthy. Combine with OptionsMethod (which reads Params for the
	// current values) for cascading selects: choose A, and B's options refetch narrowed by
	// A — and B can stay hidden until A is set. camelCase to match hope's PromptField.
	DependsOn    string `json:"dependsOn,omitempty"`
	DependsValue string `json:"dependsValue,omitempty"`
	// FieldsMethod names a Fields provider (registered with Plugin.Fields). On this field's
	// change hope calls it with the current values (via Params) and renders the returned
	// []Field inline as a sub-form below this field — so a selection produces real, typed
	// inputs rather than a free-form box. The sub-fields may carry their own OptionsMethod /
	// DependsOn. camelCase to match hope's PromptField.
	FieldsMethod string `json:"fieldsMethod,omitempty"`
}

type FieldError added in v0.5.0

type FieldError struct {
	Key   string `json:"key"`
	Error string `json:"error"`
}

FieldError is one field-level validation error returned by a Validate provider (#4). hope renders Error inline under the field named by Key and disables Run while any exist.

type FieldType added in v0.5.0

type FieldType string

Field mirrors hope's PromptField: the UI collects these before invoking an action, then passes the values map to the action handler. FieldType is the input kind of an action/wizard Field. Use the Field* consts for typo-safe forms; a bare string still works, since the const values are these strings.

const (
	FieldText        FieldType = "text"        // single-line text (the default)
	FieldTextarea    FieldType = "textarea"    // multi-line text
	FieldSelect      FieldType = "select"      // one choice from Options / OptionsMethod (dropdown)
	FieldToggle      FieldType = "toggle"      // on/off; the value is "true"/"false"
	FieldKV          FieldType = "kv"          // a key/value editor; the value is a JSON object
	FieldGroup       FieldType = "group"       // a repeatable sub-form (Fields); the value is a JSON array of rows
	FieldNumber      FieldType = "number"      // numeric input with Min/Max/Step/Unit
	FieldMultiselect FieldType = "multiselect" // multi choice as a checkbox dropdown; the value is a JSON array
	FieldChips       FieldType = "chips"       // multi choice as inline toggle pills; the value is a JSON array
	FieldCombobox    FieldType = "combobox"    // single editable type-ahead; set AllowCustom for freeform entry
)

type FieldsFunc added in v0.5.0

type FieldsFunc func(ctx context.Context) ([]Field, error)

FieldsFunc returns a dynamic sub-form for a Field whose FieldsMethod names it (#1). hope calls it on that field's change with the current values (Params) and renders the returned []Field inline below it — so a selection produces real, typed inputs, not a free-form box. The returned fields may themselves carry OptionsMethod/DependsOn.

type GraphData added in v0.6.0

type GraphData struct {
	Nodes    []*GraphNode `json:"nodes"`
	Edges    []GraphEdge  `json:"edges,omitempty"`
	Directed bool         `json:"directed,omitempty"`
	// Active is the id of the DAG this data is for — set it so hope shows/highlights the
	// active DAG (in the sidebar, toolbar) and threads it to the chrome regions and run.
	Active string `json:"active,omitempty"`
}

GraphData is what a Graph view returns: nodes positioned at x/y and edges between their ports. Build the nodes/edges with GNode/GEdge. hope renders the canvas; the plugin owns what each node contains and all persistence.

type GraphEdge added in v0.6.0

type GraphEdge struct {
	From  string `json:"from"`
	To    string `json:"to"`
	Tone  string `json:"tone,omitempty"`
	Label string `json:"label,omitempty"`
}

GraphEdge connects one node's out-port to another's in-port. From/To are "nodeID:portID".

func GEdge added in v0.6.0

func GEdge(from, to string) GraphEdge

GEdge builds an edge between "nodeID:portID" endpoints. Chain .Toned / .Labeled.

func (GraphEdge) Labeled added in v0.6.0

func (e GraphEdge) Labeled(label string) GraphEdge

Labeled sets an edge's label.

func (GraphEdge) Toned added in v0.6.0

func (e GraphEdge) Toned(tone string) GraphEdge

Toned sets an edge's tone.

type GraphMenuItem added in v0.6.0

type GraphMenuItem struct {
	Label   string `json:"label,omitempty"`
	Icon    string `json:"icon,omitempty"`
	Method  string `json:"method,omitempty"`
	Danger  bool   `json:"danger,omitempty"`
	Divider bool   `json:"divider,omitempty"` // a separator (Label ignored)
}

GraphMenuItem is one entry in the canvas right-click menu, returned by the GraphMenu method for the right-clicked target ({kind:"node"|"edge"|"canvas", id/from/to}). Clicking it calls Method with the target's row ({id}/{from,to}/{x,y}, plus the active graph). hope prepends its own built-ins (Configure / Delete node / Disconnect) when the matching methods are wired.

type GraphNode added in v0.6.0

type GraphNode struct {
	ID    string  `json:"id"`
	Type  string  `json:"type,omitempty"`
	X     float64 `json:"x"`
	Y     float64 `json:"y"`
	W     int     `json:"w,omitempty"` // node width px (0 => hope default ~190)
	Title string  `json:"title,omitempty"`
	Icon  string  `json:"icon,omitempty"` // built-in name OR a plugin Icons key
	Tone  string  `json:"tone,omitempty"` // node accent
	// Meta is a clean, ordered key/value strip hope renders on the node face (its config /
	// params) — set it from the node's settings so populated params show without hand-building
	// a Body. Build with GNode(...).Meta(label, value). Body (below) is still available for
	// fully custom node content.
	Meta  []NodeMeta     `json:"meta,omitempty"`
	Body  *Comp          `json:"body,omitempty"`
	In    []Port         `json:"in,omitempty"`
	Out   []Port         `json:"out,omitempty"`
	State string         `json:"state,omitempty"` // run overlay: idle|running|done|error (drives the node glow)
	Data  map[string]any `json:"data,omitempty"`  // opaque; echoed back to mutation methods
	// Fields is an optional per-node config form. When set (and the view has GraphConfig),
	// clicking the node opens these Fields — the SAME dynamic form as an action, so a node
	// gets real inputs: a select populated from an OptionsMethod, number, multiselect, etc.
	// The form prefills from Data (matching keys); submitting calls the GraphConfig method
	// with {id, ...values}. Build with GNode(...).Form(...).
	Fields []Field `json:"fields,omitempty"`
	// Actions render as a button bar at the bottom of the node card (Info / Logs / …). Each
	// runs its Method with {id, ...Data}; the result can steer the UI (a flyout/navigate
	// directive), so an "Info" action can open a modal explaining what the node does. Reuses
	// RowAction (Icon/Danger/Fields/Tip). Build with GNode(...).Act(...).
	Actions []RowAction `json:"actions,omitempty"`
}

GraphNode is one node on the canvas. hope ships no node types — Type is the plugin's own label for it (metadata hope never interprets); Body is an arbitrary Comp tree rendered in the node card; In/Out are the connectable ports. Build with GNode.

func GNode added in v0.6.0

func GNode(id, title string, body ...*Comp) *GraphNode

GNode builds a node: an id + title, with any body components rendered in the node card. Chain .At/.Typed/.Ico/.Toned/.Wide/.InPorts/.OutPorts/.Stated/.With.

func (*GraphNode) Act added in v0.6.0

func (n *GraphNode) Act(actions ...RowAction) *GraphNode

Act adds action buttons to the node's bottom bar (Info / Logs / …). Each runs its Method with {id, ...Data} and its result can open a flyout / navigate (an "Info" action that shows a modal of what the node does). Reuses RowAction. Chainable.

func (*GraphNode) At added in v0.6.0

func (n *GraphNode) At(x, y float64) *GraphNode

At sets the node's canvas position.

func (*GraphNode) Form added in v0.6.0

func (n *GraphNode) Form(fields ...Field) *GraphNode

Form gives the node a config form — the same dynamic Fields as an action (a select with OptionsMethod, number, multiselect, ...). Clicking the node opens it, prefilled from Data; saving calls the view's GraphConfig method with {id, ...values}.

func (*GraphNode) Ico added in v0.6.0

func (n *GraphNode) Ico(name string) *GraphNode

Ico sets the node's leading icon (a built-in name or a plugin Icons key).

func (*GraphNode) InPorts added in v0.6.0

func (n *GraphNode) InPorts(ports ...Port) *GraphNode

InPorts sets the node's input ports (rendered down the left edge).

func (*GraphNode) OutPorts added in v0.6.0

func (n *GraphNode) OutPorts(ports ...Port) *GraphNode

OutPorts sets the node's output ports (rendered down the right edge).

func (*GraphNode) Stated added in v0.6.0

func (n *GraphNode) Stated(s string) *GraphNode

Stated sets the node's run state (idle|running|done|error) — the run glow.

func (*GraphNode) Toned added in v0.6.0

func (n *GraphNode) Toned(tone string) *GraphNode

Toned sets the node's accent tone.

func (*GraphNode) Typed added in v0.6.0

func (n *GraphNode) Typed(t string) *GraphNode

Typed sets the node's plugin-defined type name (metadata; drives nothing in hope).

func (*GraphNode) Wide added in v0.6.0

func (n *GraphNode) Wide(px int) *GraphNode

Wide sets the node's width in px.

func (*GraphNode) With added in v0.6.0

func (n *GraphNode) With(data map[string]any) *GraphNode

With attaches opaque data echoed back to the mutation methods.

func (*GraphNode) WithMeta added in v0.6.0

func (n *GraphNode) WithMeta(label, value string) *GraphNode

WithMeta appends a key/value line to the node's clean meta strip (its params/config), rendered on the node face. Chainable: n.WithMeta("mode", "dedupe").WithMeta("parallel", "4").

type GraphOpt added in v0.6.0

type GraphOpt = TableOpt

GraphOpt configures a Graph view's descriptor (a functional option, composes with ViewOpt).

func GraphAdd added in v0.6.0

func GraphAdd(method string) GraphOpt

GraphAdd names the method hope calls to create a node — with the picked palette {type, x, y}, or {type:"", x, y} from a double-click on empty canvas (your default node).

func GraphConfig added in v0.6.0

func GraphConfig(method string) GraphOpt

GraphConfig names the method hope calls to persist a node's config Form: {id, ...values}. Required for GNode(...).Form(...) node inputs to save.

func GraphConnect added in v0.6.0

func GraphConnect(method string) GraphOpt

GraphConnect names the method hope calls to add an edge: {from:"nodeID:portID", to:"nodeID:portID"}.

func GraphDelete added in v0.6.0

func GraphDelete(method string) GraphOpt

GraphDelete names the method hope calls to delete a node: {id}. Confirm it with a DangerAction.

func GraphDirected added in v0.6.0

func GraphDirected() GraphOpt

GraphDirected draws arrowheads on edges (a directed graph).

func GraphDisconnect added in v0.6.0

func GraphDisconnect(method string) GraphOpt

GraphDisconnect names the method hope calls to remove an edge: {from, to}.

func GraphMenu added in v0.6.0

func GraphMenu(method string) GraphOpt

GraphMenu names the method hope calls on right-click to build a custom context menu. It receives the target ({kind:"node"|"edge"|"canvas", id/from/to}) and returns []GraphMenuItem; hope shows them under its own built-ins (Configure / Delete node / Disconnect). Each item's Method is invoked with the target's row.

func GraphMove added in v0.6.0

func GraphMove(method string) GraphOpt

GraphMove names the method hope calls to persist a dragged node's position: {id, x, y}.

func GraphNodeFlyout added in v0.6.0

func GraphNodeFlyout(method string) GraphOpt

GraphNodeFlyout names the method hope calls when a node is clicked: {id} -> a Comp body it renders in the right-side drawer. (A node with a config Form opens that form instead.)

func GraphPalette added in v0.6.0

func GraphPalette(method string) GraphOpt

GraphPalette names a method returning the []NodeType catalog — the node types the operator can drag onto the canvas. For a fixed catalog use StaticPalette instead.

func GraphRun added in v0.6.0

func GraphRun(streamMethod string) GraphOpt

GraphRun names a StreamComponent-kind stream that emits GraphData frames while a run is active — the plugin advances each node's State so hope highlights progress live.

func GraphSelect added in v0.6.0

func GraphSelect(method string) GraphOpt

GraphSelect names the method hope calls when a sidebar entry is chosen: {id}. The canvas then re-fetches with {graph: id} so selecting a DAG swaps the canvas in place.

func GraphSidebar added in v0.6.0

func GraphSidebar(method string) GraphOpt

GraphSidebar names a method returning a Comp for the left rail — the natural home for a browsable list of the plugin's DAGs. Pair with GraphSelect to switch the active graph.

func GraphSidebarActions added in v0.6.0

func GraphSidebarActions(methods ...string) GraphOpt

GraphSidebarActions renders the given action methods as buttons atop the sidebar — the home for "New pipeline" / "New folder" and the like. Each runs like any action (fields, confirm); on success hope re-fetches the sidebar + the graph.

func GraphSnap added in v0.6.0

func GraphSnap(px int) GraphOpt

GraphSnap snaps dragged nodes to an N-px grid (0 = free positioning).

func GraphToolbar added in v0.6.0

func GraphToolbar(method string) GraphOpt

GraphToolbar names a method returning a Comp for the strip above the canvas.

func GraphTypeInfo added in v0.6.0

func GraphTypeInfo(method string) GraphOpt

GraphTypeInfo names a method that returns a Comp describing a node TYPE ({type}). hope shows an info button on each palette entry that opens it in the drawer — so an operator can read what a node does before dragging it onto the canvas.

func GraphValidateConnect added in v0.6.0

func GraphValidateConnect(method string) GraphOpt

GraphValidateConnect names an optional pre-connect gate: {from, to} -> a ConfirmResult-ish {ok bool, reason string}. hope only commits the connection when ok.

func StaticPalette added in v0.6.0

func StaticPalette(types ...NodeType) GraphOpt

StaticPalette sets a fixed node-type catalog on the view (alternative to GraphPalette).

type Hope added in v0.1.0

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

Hope is a handle to the operator ACTIONS a plugin may perform on hope — the operator pattern (watch events via OnEvent, then reconcile by mutating hope). Each action requires its own operator-granted scope and hope's reverse channel; without the grant hope rejects the call, and every action is audited. Actions target the plugin's OWN stack only.

func (*Hope) AddServiceLabel added in v0.1.0

func (h *Hope) AddServiceLabel(ctx context.Context, service, key, value string) error

AddServiceLabel adds (or updates) a label on a service in the plugin's own stack and hope re-applies the stack, so the label PERSISTS across future redeploys. The classic use is a plugin that auto-adds Prometheus scrape labels on deploy. service is a service name in this plugin's stack; the plugin can't target another stack. Requires the spec:label permission.

type ImageOpt

type ImageOpt func(map[string]any)

ImageOpt configures an Image cell's render box (size + fit).

func ImgBox

func ImgBox(w, h int) ImageOpt

ImgBox fixes both dimensions: the image is centered in a w×h box and, by default, contained (scaled to fit, no crop). Add ImgFit("cover") to fill and crop instead.

func ImgCache added in v0.0.9

func ImgCache() ImageOpt

ImgCache opts THIS image into hope's durable local byte cache. hope caches the flagged image client-side via a service worker (the CORS-free way — it caches the <img>'s own request/response, so the image host needs no CORS/cooperation) and serves it from cache on later views, surviving reloads and sessions. Opt-in per image: only images you flag are cached — nothing else. Prefer it on immutable, content-addressed art (a badge/canvas keyed by hash) that's expensive to re-fetch from a remote host.

func ImgFallback

func ImgFallback(url string) ImageOpt

ImgFallback sets an image shown when src fails to load (broken/404/blocked). It must also be an absolute http(s) URL. Tried once; if it too fails, the cell renders blank (no browser broken-image icon).

func ImgFit

func ImgFit(fit string) ImageOpt

ImgFit sets object-fit: "contain" (default — whole image, letterboxed) or "cover" (fill the box, cropping overflow).

func ImgH

func ImgH(px int) ImageOpt

ImgH fixes the image height in px; width is auto (keeps aspect).

func ImgLightbox

func ImgLightbox() ImageOpt

ImgLightbox makes clicking the image open it in an in-app lightbox (a dimmed full-screen overlay, closed by backdrop click / Esc / the close button) instead of a new browser tab — nicer for viewing badge/avatar art in place.

func ImgW

func ImgW(px int) ImageOpt

ImgW fixes the image width in px; height is auto, so the aspect ratio is kept (e.g. "240×N"). Combine with ImgH for a fixed box (see ImgBox).

type InitContext added in v0.0.7

type InitContext struct {
	Settings map[string]string
	Protocol int
	Caps     Capabilities
}

InitContext is what hope hands a plugin at initialization (hope.init): the settings the operator configured (so the plugin can set up WITH them), plus the hope build's protocol version and capabilities.

type Item added in v0.5.0

type Item struct {
	Type string         `json:"type,omitempty"`
	Data map[string]any `json:"data,omitempty"`
	Comp *Comp          `json:"comp,omitempty"`
}

Item is one entry in a paged collection (see ViewKind Paged). Resolution per item:

  • Comp set (A): hope renders that component tree directly — a one-off / special item.
  • else Type set (B): hope binds Data into the view's ItemTemplate for that Type.
  • else: Data renders as key/values (a sensible default so an untyped item still shows).

type KVData

type KVData = map[string]any

KVData is what a KV view returns: a flat map of label -> value. A value may be a plain scalar or a rich cell (Badge/Link/Image/…). It's a named alias so a KV handler reads as returning KVData, while a map literal still satisfies it.

type Layout

type Layout struct {
	ProtocolVersion int            `json:"protocolVersion"`
	Contributions   []Contribution `json:"contributions"`
}

Layout is the result of the fixed hope.layout method: the plugin's UI contributions, versioned for graceful cross-version degradation.

type Match

type Match struct {
	Always   bool              `json:"always,omitempty"`   // every container
	Images   []string          `json:"images,omitempty"`   // image-ref globs, e.g. "postgres*"
	Labels   map[string]string `json:"labels,omitempty"`   // label == value
	Services []string          `json:"services,omitempty"` // compose service names
}

Match decides which containers a container/stack contribution applies to. The plugin declares this (hope does not map containers to plugins). A nil/empty Match means "the plugin's own container" — the trivial self-describing case. Semantics: set clauses are AND-ed; values within a clause are OR-ed.

type Node

type Node struct {
	Kind  NodeKind `json:"kind"`
	Title string   `json:"title,omitempty"`
	Ref   string   `json:"ref,omitempty"`  // leaf: method name of a view/action/stream
	Size  int      `json:"size,omitempty"` // optional row/grid weight
	Fill  bool     `json:"fill,omitempty"` // grow to fill the remaining height (e.g. a table)
	// Collapsible makes a titled section fold on a title click; Collapsed starts it
	// closed. For dense pages where not everything needs to be open at once.
	Collapsible bool    `json:"collapsible,omitempty"`
	Collapsed   bool    `json:"collapsed,omitempty"`
	Children    []*Node `json:"children,omitempty"`
	// Comp carries an inline Component tree when Kind is NodeComponent (see Component).
	Comp *Comp `json:"comp,omitempty"`
}

Node is one node in the surface-agnostic layout tree. The same tree drives a container panel now and a full page later; hope's renderer walks it without caring which surface hosts it.

func Buttons added in v0.0.3

func Buttons(refs ...string) *Node

Buttons builds a horizontal group of action buttons from action method refs. The buttons size to content and sit together (a toolbar), unlike Row's stretched columns — e.g. Buttons("analyze", "vacuum") for a maintenance section.

func Component added in v0.0.6

func Component(c *Comp) *Node

Component builds an inline component node: a Comp tree (see component.go) rendered straight from the layout, with no per-view round-trip. Use it for a small static tile — e.g. plugin.Component(plugin.Box(plugin.Heading("Fleet", 3), …)).

func Grid

func Grid(children ...*Node) *Node

Grid builds a grid from children.

func Leaf

func Leaf(ref string) *Node

Leaf references a registered view/action/stream by its method name.

func Row

func Row(children ...*Node) *Node

Row builds a horizontal row from children — equal-width columns (each child gets an equal flex share). For a group of action buttons use Buttons instead, so they size to content and don't spread across the row.

func Section

func Section(title string, children ...*Node) *Node

Section builds a titled section node from children.

func Tabs

func Tabs(children ...*Node) *Node

Tabs builds a tabbed node from children (each child's Title is its tab label).

func (*Node) Collapse

func (n *Node) Collapse(collapsed bool) *Node

Collapse makes a titled section fold on a title click. Pass collapsed=true to start it closed.

func (*Node) Filled

func (n *Node) Filled() *Node

Filled marks a node to grow and fill the remaining height (and propagates up its ancestors when rendered) — e.g. a table that should fill the page.

func (*Node) Titled

func (n *Node) Titled(t string) *Node

Titled sets a node's title (useful for wrapping a Leaf inside Tabs).

func (*Node) Weight

func (n *Node) Weight(w int) *Node

Weight sets a child's flex weight inside a Row (or Grid) — the WIDTH proportion it takes relative to its siblings. Default (0) means equal share. e.g. in a two-column Row, Weight(1) beside Weight(2) makes the second column twice as wide:

plugin.Row(
    plugin.Section("Overview", plugin.Leaf("head")).Weight(1),
    plugin.Section("Fields", plugin.Leaf("fields")).Weight(2),
)

type NodeKind

type NodeKind string

NodeKind enumerates layout-tree node types. Containers hold Children; a leaf references a registered view/action/stream by method name.

const (
	NodeSection NodeKind = "section" // titled group
	NodeTabs    NodeKind = "tabs"    // tabbed children
	NodeRow     NodeKind = "row"     // horizontal arrangement (equal-width columns)
	NodeButtons NodeKind = "buttons" // horizontal group of action buttons, sized to content
	NodeGrid    NodeKind = "grid"    // grid arrangement
	NodeLeaf    NodeKind = "leaf"    // a single view/action/stream
	// NodeComponent carries an inline Comp tree (see component.go) rendered directly
	// from the layout — no ref, no per-view round-trip. Build it with Component().
	NodeComponent NodeKind = "component"
)

type NodeMeta added in v0.6.0

type NodeMeta struct {
	Label string `json:"label"`
	Value string `json:"value"`
	Tone  string `json:"tone,omitempty"`
}

NodeMeta is one line in a node's clean key/value meta strip (its params/config).

type NodeType added in v0.6.0

type NodeType struct {
	Type     string `json:"type"`
	Label    string `json:"label,omitempty"`
	Icon     string `json:"icon,omitempty"`
	Tone     string `json:"tone,omitempty"`
	Category string `json:"category,omitempty"` // groups the palette
	Desc     string `json:"desc,omitempty"`     // tooltip
}

NodeType is one entry in the palette — a kind of node the plugin offers to place. Dragging it onto the canvas calls the GraphAdd method with {type,x,y}. Return these from GraphPalette (or set a static Palette on the view).

type Option

type Option struct {
	Label string `json:"label"`
	Value string `json:"value"`
}

Option is a select choice, mirroring hope's PromptOption.

type OptionsFunc added in v0.3.0

type OptionsFunc func(ctx context.Context) ([]Option, error)

OptionsFunc returns the choices for an RPC-populated select field. hope calls it (proxied like any method) to fill a Field whose OptionsMethod names it, instead of the author hard-coding a static list. The current partial form values are available via Params(ctx), so a later cascading select can narrow its options by an earlier field.

type Page added in v0.5.0

type Page struct {
	Items      []Item `json:"items"`
	Total      int    `json:"total,omitempty"`
	NextCursor string `json:"nextCursor,omitempty"`
}

Page is one page of a Paged view, returned by its method. The method reads the requested page from Params(ctx): "offset"+"limit" (offset paging) or "cursor" (cursor paging), plus "filter". Return Total for offset paging (drives the pager) OR NextCursor for cursor paging (drives infinite scroll); an empty NextCursor means the last page.

type PageItem

type PageItem struct {
	Title    string         `json:"title"`
	Icon     string         `json:"icon,omitempty"`
	Param    map[string]any `json:"param,omitempty"`
	Children []PageItem     `json:"children,omitempty"`
}

PageItem is one dynamic subpage: it shares the contribution's Node but carries its own Param, which hope merges into every call the page makes — so a plugin can list e.g. every DB table as a rail entry that renders the same view with a different argument. Read it in a handler with plugin.Params(ctx, &v).

Children nests items one level (a group node), so e.g. three databases each listing their tables become nested rail entries. A node with Children is a group (not itself a page); a leaf node (no Children) is the navigable page.

type Permission added in v0.1.0

type Permission struct {
	Scope  string `json:"scope"`
	Reason string `json:"reason,omitempty"`
}

Permission is a reverse capability the plugin REQUESTS from hope. Least privilege: a plugin gets NOTHING on the plugin->hope direction unless it declares the scope here (via Plugin.RequirePermission) AND the operator consents when enabling it. hope records the granted subset and gates every reverse call on it; the plugin's token authenticates identity, the grant set authorizes. Reason is shown on the operator's consent prompt.

type Plugin

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

Plugin is a hope plugin server. Construct with New, register capabilities, then ListenAndServe. Registration is not safe for concurrent use; serving is.

func New

func New(name, version string) *Plugin

New creates a plugin with the given name and semantic version. The endpoint path defaults to /__hope and the request body cap to 4 MiB. If the environment sets HOPE_PLUGIN_TOKEN, calls must present it as a bearer token.

func (*Plugin) Action

func (p *Plugin) Action(method, label string, fields []Field, fn ActionFunc, opts ...ActionOpt) *Plugin

Action registers an invocable action. The UI collects fields, then calls fn with the values. Mark destructive actions with DangerAction.

func (*Plugin) Alert added in v0.1.0

func (p *Plugin) Alert(ctx context.Context, severity, title, detail, dedupeKey string) error

Alert is a convenience over Publish: it publishes an event whose Data is {severity, title, detail, dedupeKey}. hope namespaces the kind to plugin.<identity>.alert. Requires events:publish.

func (*Plugin) Breadcrumbs

func (p *Plugin) Breadcrumbs(crumbs ...Crumb) *Plugin

Breadcrumbs attaches a breadcrumb trail to the most recently added page contribution (call right after Page/DetailPage). {param} placeholders in a crumb's label/to are filled from the page param.

func (*Plugin) CardsView

func (p *Plugin) CardsView(method, label string, fn ViewFunc, opts ...ViewOpt) *Plugin

CardsView registers a Cards view; the handler returns CardsData (a grid of cards / a gallery — e.g. badges, users). Cards with a To navigate on click. opts (Static/EmptyView/…) are optional.

func (*Plugin) ChartView

func (p *Plugin) ChartView(method, label string, fn ViewFunc, opts ...ViewOpt) *Plugin

ChartView registers a Chart view; the handler returns ChartData (bar or line, one or more named series over categorical labels). hope draws axes + legend. opts (Static/EmptyView/…) are optional.

func (*Plugin) ComponentView added in v0.0.6

func (p *Plugin) ComponentView(method, label string, fn ViewFunc, opts ...ViewOpt) *Plugin

ComponentView registers a Component view — the escape hatch. The handler returns a *Comp: a tree of safe primitives (box/row/heading/keyval/sparkline/cell/…) hope composes into a custom widget the built-in kinds don't cover (see component.go). Add plugin.Static() to cache it or plugin.EmptyView(...) to customize its empty state. For a small STATIC tile, prefer an inline plugin.Component node in the layout instead — it needs no per-view round-trip.

func (*Plugin) Confirm added in v0.5.0

func (p *Plugin) Confirm(method string, fn ConfirmFunc) *Plugin

Confirm registers an impact-confirmation provider under method. Point an action's ConfirmMethod at it (via ActionConfirm). Shares the plugin's method namespace.

func (*Plugin) ContainerPanel

func (p *Plugin) ContainerPanel(title string, match *Match, node *Node) *Plugin

ContainerPanel is a shorthand for a container-surface contribution with the given title, match, and layout node.

func (*Plugin) Contribute

func (p *Plugin) Contribute(c Contribution) *Plugin

Contribute adds an explicit UI contribution. Without any, the plugin auto-generates a single container contribution (see layout) listing every registered capability — so a minimal plugin needs no layout code.

func (*Plugin) DangerAction

func (p *Plugin) DangerAction(method, label string, fields []Field, fn ActionFunc, opts ...ActionOpt) *Plugin

DangerAction is like Action but flags the action destructive, so hope confirms before running it and audit-logs the invocation.

func (*Plugin) DashboardWidget

func (p *Plugin) DashboardWidget(title string, node *Node) *Plugin

DashboardWidget contributes a widget to hope's fleet/host dashboard (the `dashboard` surface): the node renders as a compact panel alongside hope's own dashboard cards. Keep it small — a couple of kv/counter/series leaves.

func (*Plugin) Description

func (p *Plugin) Description(s string) *Plugin

Description sets a one-line description shown in hope.

func (*Plugin) DetailPage

func (p *Plugin) DetailPage(id, title, paramKey string, node *Node) *Plugin

DetailPage contributes a hidden master-detail page addressed by a stable id (not shown in the rail). A Link/DetailLink navigates to it plugin-relative, and hope passes the URL arg as param[paramKey] — read it in a handler with plugin.Params. e.g. DetailPage("user", "User", "id", node) rendered at .../user/42 => {id:"42"}.

func (*Plugin) DynamicPage

func (p *Plugin) DynamicPage(title string, node *Node, items []PageItem) *Plugin

DynamicPage contributes MANY rail pages that share one layout node but each pass a distinct Param (merged into every call the page makes). items may nest one level (groups of pages) — e.g. databases -> tables. Read the param in a handler with plugin.Params. Regenerate the items in getLayout to reflect live state.

func (*Plugin) DynamicPageFunc added in v0.0.2

func (p *Plugin) DynamicPageFunc(title string, node *Node, fn func(ctx context.Context) []PageItem) *Plugin

DynamicPageFunc is DynamicPage with LIVE items: fn runs on every hope.layout to produce the rail entries, so the set reflects current state (a database's tables, a broker's topics) instead of a snapshot frozen at startup — the answer to "my pages depend on data I don't have until runtime." Keep fn fast: hope fetches the layout per surface, so cache if it hits a slow backend.

func (*Plugin) Fields added in v0.5.0

func (p *Plugin) Fields(method string, fn FieldsFunc) *Plugin

Fields registers a selection->sub-form provider under method. Point a Field's FieldsMethod at it. The method shares the plugin's method namespace (like views/actions/options), so keep it distinct.

func (*Plugin) GraphView added in v0.6.0

func (p *Plugin) GraphView(method, label string, fn ViewFunc, opts ...GraphOpt) *Plugin

GraphView registers a Graph view (clone of ChartView with Kind: Graph). The function returns a *GraphData. Pair with the Graph* opts to wire editing, a node-click flyout, a run stream, and the plugin-controlled chrome (sidebar / toolbar / palette). A bare GraphView with no mutation opts is a read-only canvas.

func (*Plugin) Handler

func (p *Plugin) Handler() http.Handler

Handler returns the http.Handler that serves the JSON-RPC endpoint at the plugin's path. Mount it yourself, or use ListenAndServe.

func (*Plugin) HeaderActions

func (p *Plugin) HeaderActions(refs ...string) *Plugin

HeaderActions attaches action buttons to the most recently added contribution — a page/panel/dashboard toolbar (distinct from leaf actions inside the layout). Each ref names a registered Action; hope collects its fields, confirms danger, and audits. Call it right after Page/ContainerPanel/DashboardWidget.

func (*Plugin) Hope added in v0.1.0

func (p *Plugin) Hope() *Hope

Hope returns the operator-actions handle.

func (*Plugin) Icon

func (p *Plugin) Icon(name string) *Plugin

Icon sets the plugin's default icon — a hope built-in name or one of the Icons keys registered with Icons.

func (*Plugin) Icons

func (p *Plugin) Icons(m map[string]string) *Plugin

Icons registers plugin-scoped icons: name -> inner SVG markup (path/circle/rect elements only, NOT a full <svg>), 24x24 stroke to match hope's icon set. hope resolves and sanitizes these in a per-plugin namespace, so they can't collide with other plugins or shadow hope's built-ins.

func (*Plugin) ListenAndServe

func (p *Plugin) ListenAndServe(addr string) error

ListenAndServe serves the plugin on addr (e.g. ":8080"). Blocks. A ReadHeaderTimeout guards against a slow-header (Slowloris) client holding a goroutine; no WriteTimeout is set because stream handlers write indefinitely.

func (*Plugin) MaxBodyBytes

func (p *Plugin) MaxBodyBytes(n int64) *Plugin

MaxBodyBytes overrides the request body cap (default 4 MiB).

func (*Plugin) OnEvent added in v0.1.0

func (p *Plugin) OnEvent(fn EventFunc) *Plugin

OnEvent registers a handler for hope events (stack/container/image/plugin/agent changes, and plugin-published events). hope pushes each relevant event as a unary hope.event call. Registering a handler auto-declares the events:subscribe permission, so the operator is asked to consent on enable; without the grant hope never calls it. Call RequirePermission(ScopeEventsSubscribe, "<reason>") first to set a custom consent reason.

func (*Plugin) OnInit added in v0.0.7

func (p *Plugin) OnInit(fn func(ctx context.Context, in InitContext) error) *Plugin

OnInit registers a handler hope calls once the plugin is reachable and being initialized (hope.init) — and again if the plugin restarts. It receives the current settings, so a plugin that needs its config at startup (a pool sized by a setting, a mode flag) can initialize with them instead of booting on defaults and waiting for a later hope.settings push. Optional: without it, settings are still applied so SettingValue works immediately. Errors are returned to hope (the install surfaces them). Register before ListenAndServe.

func (*Plugin) OnStatus added in v0.3.0

func (p *Plugin) OnStatus(fn func(ctx context.Context) StatusReport) *Plugin

OnStatus registers a handler hope calls (via the reserved hope.status method) to read the plugin's ADVISORY self-reported health — domain state only the plugin knows (replica count, replication lag, upstream reachability). Pull-only: hope asks, the plugin answers, so no reverse channel or grant is involved. An older hope that lacks the "status" feature simply never calls it — check Caps(ctx).Supports("status") if the plugin wants to know whether this hope will ask.

func (*Plugin) Options added in v0.3.0

func (p *Plugin) Options(method string, fn OptionsFunc) *Plugin

Options registers an RPC-populated select provider under method. Reference it from a form Field with OptionsMethod: method, and hope fetches the choices live when it renders the form. The method name shares the plugin's method namespace (like views and actions), so keep it distinct.

func (*Plugin) Page

func (p *Plugin) Page(title string, node *Node) *Plugin

Page contributes a full custom nav page (the `page` surface). hope lists it in the rail under the plugin and renders the node tree as a full page.

func (*Plugin) PageID

func (p *Plugin) PageID(id string) *Plugin

PageID gives the most recently added page contribution a stable id so links and breadcrumbs can target it by name (a plugin doesn't know positional page paths). Call right after Page.

func (*Plugin) PagedView added in v0.5.0

func (p *Plugin) PagedView(method, label string, fn ViewFunc, opts ...ViewOpt) *Plugin

PagedView registers a SERVER-PAGED collection view. The method reads the requested page from Params(ctx) — "offset"+"limit" (offset paging) or "cursor" (cursor paging), plus an optional "filter" — and returns a *Page. Each item renders via its Comp (A) or an ItemTemplate for its Type (B). Pair with Layout / Infinite / ItemTemplate / PageSize.

func (*Plugin) Path

func (p *Plugin) Path(path string) *Plugin

Path overrides the endpoint path (default /__hope). Must match the hope.plugin.path label.

func (*Plugin) Publish added in v0.1.0

func (p *Plugin) Publish(ctx context.Context, e Event) error

Publish emits an event onto hope's bus. Requires (a) the events:publish permission granted by the operator and (b) hope's reverse channel configured. hope stamps the Source and namespaces the Kind (plugin.<identity>.<kind>), so only Kind + Data here are meaningful — a plugin can never spoof another's events or a core hope kind.

func (*Plugin) QueryView

func (p *Plugin) QueryView(method, label, lang, def string, fn ViewFunc, opts ...ViewOpt) *Plugin

QueryView registers a Query view whose input editor syntax-highlights lang and prepopulates with def (a template where {param} placeholders are filled from the page's param, e.g. "select * from {table}"). Read the input with plugin.Input.

func (*Plugin) RequestPermission added in v0.1.0

func (p *Plugin) RequestPermission(ctx context.Context, scope, reason string) error

RequestPermission asks the operator, at runtime, to grant a scope this plugin doesn't yet hold (the Android runtime-permission model). It does not grant anything — hope raises a consent prompt; the plugin gains the capability only if the operator allows it. Safe to call repeatedly: an already-granted/denied/pending scope is a no-op on hope's side. Declaring the scope up front via RequirePermission (so it's asked at enable) is usually better; use this when a capability is only needed conditionally at runtime.

func (*Plugin) RequirePermission added in v0.1.0

func (p *Plugin) RequirePermission(scope, reason string) *Plugin

RequirePermission declares that the plugin wants a reverse capability (an events:*/storage/... scope). It appears in hope.schema; the operator consents when enabling the plugin, and hope gates the capability on the grant. Idempotent per scope — a later call only updates the reason if one is given.

func (*Plugin) Resolve added in v0.3.0

func (p *Plugin) Resolve(method string, fn ResolveFunc) *Plugin

Resolve registers a selector->surface provider under method. Point a form Field's ResolveMethod at it: when the form's values change, hope calls this with the current values and renders the returned component node inline. The method shares the plugin's method namespace (like views/actions/options), so keep it distinct.

func (*Plugin) ResolveAlert added in v0.1.0

func (p *Plugin) ResolveAlert(ctx context.Context, title, dedupeKey string) error

ResolveAlert clears a previously-raised alert (matched by dedupeKey): hope surfaces it as a "resolved" confirmation and drops the alert from any active view. Use it when a monitored condition recovers. Requires events:publish.

func (*Plugin) SearchView

func (p *Plugin) SearchView(method, label string, fn ViewFunc) *Plugin

SearchView registers a Search (autocomplete) view: hope calls fn with {q: <text>} as the user types and renders the returned SearchData as a live dropdown; selecting a SearchItem navigates to its To. Read the query with plugin.SearchQuery(ctx). Great for a "go to <entity>" jump box.

func (*Plugin) Setting

func (p *Plugin) Setting(s Setting) *Plugin

Setting declares an operator-managed configuration field. hope renders these in the plugin inspector, persists the values (encrypted), and pushes them to the plugin; read a value in any handler with SettingValue. Settings are config the operator SETS — distinct from the panel the plugin SHOWS.

func (*Plugin) SettingValue

func (p *Plugin) SettingValue(key string) string

SettingValue returns the current value of an operator-managed setting (the value hope last pushed), falling back to the declared default. Safe for concurrent use.

func (*Plugin) StackWidget added in v0.0.2

func (p *Plugin) StackWidget(title string, match *Match, node *Node) *Plugin

StackWidget contributes a widget to the stack view (the `stack` surface): the node renders as a panel on a stack's page, for stacks whose containers the Match selects. It's the container panel's whole-stack analog — same Match semantics (images/labels/services/always), but evaluated against the stack's set of containers rather than one. Keep it focused: a stack-scoped overview or action.

func (*Plugin) StatView

func (p *Plugin) StatView(method, label string, fn ViewFunc, opts ...ViewOpt) *Plugin

StatView registers a Stat view: the handler returns StatData (one or more big-number blocks — counts, totals, sizes). Add plugin.Refreshable() for a manual refresh button (e.g. "count rows in my table" on demand).

func (*Plugin) Storage added in v0.1.0

func (p *Plugin) Storage() *Storage

Storage returns this plugin's storage handle.

func (*Plugin) Stream

func (p *Plugin) Stream(method, label string, kind StreamKind, fn StreamFunc) *Plugin

Stream registers a live stream emitted as NDJSON frames.

func (*Plugin) Subtitle

func (p *Plugin) Subtitle(s string) *Plugin

Subtitle sets the page header's sub/meta line for the most recently added page contribution ({param} placeholders filled from the page param). Call after Page.

func (*Plugin) TableView

func (p *Plugin) TableView(method, label string, fn ViewFunc, opts ...TableOpt) *Plugin

TableView registers a Table view. Add RowDetail/RowActions opts to make rows interactive — e.g. TableView("rows","Rows",fn, plugin.RowDetail("inspect"), plugin.RowActions(plugin.RowAction{Method:"del",Label:"Delete",Danger:true})).

func (*Plugin) TextView

func (p *Plugin) TextView(method, label string, fn ViewFunc, opts ...ViewOpt) *Plugin

TextView registers a Text view: the handler returns {text: "…"} (or a raw string) rendered as a monospace scrollable block — logs, config, command output. opts (Static/EmptyView/…) are optional.

func (*Plugin) Token

func (p *Plugin) Token(token string) *Plugin

Token pins the shared secret hope must present as a bearer token, overriding HOPE_PLUGIN_TOKEN. Use when you configure the token in code rather than env.

func (*Plugin) Validate added in v0.5.0

func (p *Plugin) Validate(method string, fn ValidateFunc) *Plugin

Validate registers a pre-submit validation provider under method. Point an action's ValidateMethod at it (via ActionValidate). Shares the plugin's method namespace.

func (*Plugin) View

func (p *Plugin) View(method, label string, kind ViewKind, fn ViewFunc, opts ...ViewOpt) *Plugin

View registers a read-only data view rendered per kind. opts (Static/EmptyView/ Refreshable/RefreshEvery) are optional and apply to any kind.

type Port added in v0.6.0

type Port struct {
	ID    string `json:"id"`
	Label string `json:"label,omitempty"`
	Kind  string `json:"kind,omitempty"`
	Tone  string `json:"tone,omitempty"`
}

Port is a connectable point on a node's edge (input on the left, output on the right). Kind is the plugin's typing used by GraphValidateConnect to accept/reject a connection.

func GPort added in v0.6.0

func GPort(id, label string) Port

GPort builds a port. Chain .Kinded (typing for connect-validation) / .Toned.

func (Port) Kinded added in v0.6.0

func (p Port) Kinded(kind string) Port

Kinded sets a port's connection type (matched by GraphValidateConnect).

func (Port) Toned added in v0.6.0

func (p Port) Toned(tone string) Port

Toned sets a port's tone. (Value receiver: use in a builder chain, e.g. GPort(...).Toned("ok").)

type ResolveFunc added in v0.3.0

type ResolveFunc func(ctx context.Context) (any, error)

ResolveFunc returns a component node (built with the Box/KeyVal/Heading/Badge/Text primitives) for a selector->surface field. hope renders whatever it returns inline in the form, below the fields, and re-calls it whenever the form values change — so a selection can drive a live preview, a confirmation, or a result. Read the current values with Params(ctx) to render for the chosen option.

type RowAction

type RowAction struct {
	Method string   `json:"method"`
	Label  string   `json:"label"`
	Icon   string   `json:"icon,omitempty"`
	Danger bool     `json:"danger,omitempty"` // hope confirms before running and audit-logs it
	Fields []Field  `json:"fields,omitempty"` // optional input collected before the call
	Tip    *Tooltip `json:"tip,omitempty"`    // hover tooltip on the row action button (build with Tip)
	// ShowWhenKey/ShowWhenValue gate the button PER ROW against that row's cells: the
	// button shows only when the row's ShowWhenKey cell equals ShowWhenValue (or, when
	// ShowWhenValue is empty, when that cell is non-empty/truthy). Mirrors Field.DependsOn/
	// DependsValue. Applies to the inline row button AND the RowFlyout footer. Empty
	// ShowWhenKey = always shown (unchanged). A Badge/Code/Link cell compares by its text.
	ShowWhenKey   string `json:"showWhenKey,omitempty"`
	ShowWhenValue string `json:"showWhenValue,omitempty"`
	// DisableInsteadOfHide renders the button disabled (greyed, Tip as the reason) instead
	// of hidden when the predicate fails — so the operator sees it exists but can't use it.
	DisableInsteadOfHide bool `json:"disableInsteadOfHide,omitempty"`
}

RowAction is one author-declared action bound to a table row. hope calls Method with {row: {column: value}} (plus the page param); use for row-scoped mutations. If Fields is set, hope collects them first and merges the values into the call params alongside row — e.g. a "Rename" action with a new-name field.

type Schema

type Schema struct {
	ProtocolVersion int               `json:"protocolVersion"`
	Name            string            `json:"name"`
	Version         string            `json:"version"`
	Description     string            `json:"description,omitempty"`
	Icon            string            `json:"icon,omitempty"`  // default icon: a hope built-in OR an Icons key
	Icons           map[string]string `json:"icons,omitempty"` // plugin-scoped {name: inner-svg-markup}
	Actions         []ActionDesc      `json:"actions"`
	Views           []ViewDesc        `json:"views"`
	Streams         []StreamDesc      `json:"streams"`
	Settings        []Setting         `json:"settings,omitempty"`    // operator-managed config (see Setting)
	Permissions     []Permission      `json:"permissions,omitempty"` // reverse-capability requests (see Permission)
}

Schema is the plugin's identity + capability manifest — the result of the fixed hope.schema method. It is the only method hope calls before the operator enables the plugin (discovery), so it must be safe to expose unauthenticated.

type SearchData

type SearchData struct {
	Items []SearchItem `json:"items"`
}

SearchData is what a Search view returns for a query: the current suggestion list.

type SearchItem

type SearchItem struct {
	Label string `json:"label"`
	Sub   string `json:"sub,omitempty"`
	Image string `json:"image,omitempty"`
	To    string `json:"to"`
}

SearchItem is one autocomplete suggestion. Label is the primary text; Sub is a dim secondary line (e.g. an id); Image is an optional thumbnail (absolute http(s) URL); To is where selecting it navigates — plugin-relative like a Link/DetailLink cell (e.g. "creator/42").

type Setting

type Setting struct {
	Key     string      `json:"key"`
	Label   string      `json:"label"`
	Kind    SettingKind `json:"kind,omitempty"` // default text
	Default string      `json:"default,omitempty"`
	Hint    string      `json:"hint,omitempty"`
	Options []Option    `json:"options,omitempty"` // for kind=select
}

Setting is one operator-managed configuration field the plugin exposes. Unlike an action's Fields (per-invocation input), settings are configured once in the plugin inspector, persisted by hope (encrypted at rest), and pushed to the plugin via the reserved hope.settings method — read them in a handler with plugin.SettingValue(key). This is distinct from the plugin's rendered panel & metrics, which live on the container inspector: settings are what the operator CONFIGURES, the panel is what the plugin SHOWS.

type SettingKind

type SettingKind string

SettingKind enumerates the input type for an operator-managed plugin setting.

const (
	SettingText     SettingKind = "text"
	SettingTextarea SettingKind = "textarea"
	SettingSelect   SettingKind = "select"
	SettingToggle   SettingKind = "toggle"
	SettingNumber   SettingKind = "number"
	SettingSecret   SettingKind = "secret" // masked input; hope stores it encrypted, never renders it back
)

type SortSpec

type SortSpec struct {
	Column string `json:"column"`
	Dir    string `json:"dir"`
}

SortSpec is a column + direction ("asc" | "desc"). Used for a table's DefaultSort.

type StatBlock

type StatBlock struct {
	Label string   `json:"label"`
	Value any      `json:"value"`
	Unit  string   `json:"unit,omitempty"`
	Sub   string   `json:"sub,omitempty"`
	Tone  string   `json:"tone,omitempty"` // ok|warn|bad|info
	Icon  string   `json:"icon,omitempty"`
	Tip   *Tooltip `json:"tip,omitempty"` // hover tooltip explaining the metric (build with Tip)
}

StatBlock is one stat: a big Value with a Label, optional Unit, a Sub line (e.g. a delta or context), a semantic Tone, and an optional Icon.

type StatData

type StatData struct {
	Stats []StatBlock `json:"stats"`
}

StatData is what a Stat view returns: one or more big-number stat blocks (counts, totals, sizes). Return {stats: [...]} for a row, or a single Stat.

type StatusReport added in v0.3.0

type StatusReport struct {
	Status string `json:"status"`           // short human status ("primary", "3/3 replicas", "lag 4s")
	Level  string `json:"level"`            // ok | info | warn | error
	Detail string `json:"detail,omitempty"` // optional longer explanation
}

StatusReport is a plugin's self-reported health, returned from the OnStatus handler when hope calls the reserved hope.status method. hope owns liveness (whether the plugin is reachable at all) and treats this as ADVISORY domain health it has no knowledge of: it renders Status/Detail and colors by Level, nothing more. hope caps the string lengths and clamps Level to the known set.

type Storage added in v0.1.0

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

Storage is a handle to this plugin install's durable key/value store, persisted by hope. Values are opaque JSON hope never interprets, namespaced to this install's stable identity (two installs of the same image, or the same install on different hosts, are isolated; replicas of one install share it — last write wins). Requires the storage permission and hope's reverse channel; every op returns ErrNoReverseChannel until hope.init delivers a callback URL.

Use it for config a stateless plugin has nowhere else to keep — e.g. alert rules the operator defined. It's for small config, not bulk data (hope caps value size).

func (*Storage) Delete added in v0.1.0

func (s *Storage) Delete(ctx context.Context, key string) error

Delete removes key.

func (*Storage) Get added in v0.1.0

func (s *Storage) Get(ctx context.Context, key string, out any) (bool, error)

Get decodes the value at key into out. Returns (false, nil) when the key is absent.

func (*Storage) List added in v0.1.0

func (s *Storage) List(ctx context.Context, prefix string) ([]string, error)

List returns the keys under prefix (empty prefix = all this plugin's keys).

func (*Storage) Set added in v0.1.0

func (s *Storage) Set(ctx context.Context, key string, v any) error

Set stores v (JSON-encoded) at key.

type StreamDesc

type StreamDesc struct {
	Method string     `json:"method"`
	Label  string     `json:"label"`
	Kind   StreamKind `json:"kind"`
	Icon   string     `json:"icon,omitempty"`
}

StreamDesc describes a live stream and how to render it.

type StreamFunc

type StreamFunc func(ctx context.Context, emit EmitFunc) error

StreamFunc emits frames until it returns or ctx is cancelled (which hope does the moment the UI disconnects — so a dropped viewer never leaves you emitting forever). Always select on ctx.Done() in long loops.

type StreamKind

type StreamKind string

StreamKind tells hope how to render a live NDJSON stream.

const (
	Counter         StreamKind = "counter"   // number(s) ticking -> stat
	Log             StreamKind = "log"       // append-only lines
	Series          StreamKind = "series"    // time series -> sparkline
	StreamComponent StreamKind = "component" // each frame is a component tree (Box/KeyVal/...) hope renders live — full custom output
)

type Surface

type Surface string

Surface names a mount point in the hope UI. The schema defines them all so the wire is forward-compatible; hope renders the surfaces it knows and silently ignores any it doesn't, so a newer plugin degrades gracefully on older hope.

const (
	SurfaceContainer Surface = "container" // panel/tab in the container inspector
	SurfacePage      Surface = "page"      // full custom nav page (+ dynamic nested pages)
	SurfaceRail      Surface = "rail"      // rail/nav entry + actions
	SurfaceDashboard Surface = "dashboard" // fleet/host dashboard widget
	SurfaceStack     Surface = "stack"     // stack-view widget (matched to the stack's containers)
	SurfaceCommand   Surface = "command"   // command-palette entry
)

type TableData

type TableData struct {
	Columns []string `json:"columns"`
	Rows    [][]any  `json:"rows"`
	Total   int      `json:"total"`
	Hidden  []string `json:"hidden,omitempty"`
	// ColumnTips maps a column name to a hover tooltip on its header — for clarifying
	// a terse or computed column (e.g. "health": Tip("seq-scan / bloat state")).
	ColumnTips map[string]*Tooltip `json:"column_tips,omitempty"`
	// RowMethod makes rows clickable, opening the row-detail modal via that method —
	// the data-side equivalent of the RowDetail table option (for views without opts,
	// like a QueryView).
	RowMethod string `json:"row_method,omitempty"`
	// RowFlyout makes rows clickable into a right-side DRAWER instead of a modal. The
	// method receives the clicked {row} and returns a component tree (plugin.Box(...)) hope
	// renders in the flyout. Data-side equivalent of the RowFlyout table option. When both
	// RowFlyout and RowMethod are set, the flyout wins.
	RowFlyout string `json:"row_flyout,omitempty"`
	// RowFlyoutWidth sizes the drawer the flyout opens in: a preset ("small" | "large" |
	// "xlarge") or an explicit px value ("680"). Empty = the default (~460px).
	RowFlyoutWidth string `json:"row_flyout_width,omitempty"`
	// RowFlyoutRefresh, when > 0, makes an OPEN flyout re-invoke its method every N seconds
	// with the same row and swap the body in place (scroll preserved) — so a live drawer
	// (e.g. a lifecycle Timeline) follows along without reopening. 0 = one-shot (default).
	RowFlyoutRefresh int `json:"row_flyout_refresh,omitempty"`
	// Flush renders the table full-height with no toolbar (filter/pager) and no inner scroll
	// box — it flows and the containing panel/flyout scrolls instead. Meant for an embedded
	// CTable showing a bounded list (e.g. the badges on a canvas), not a big paged view.
	Flush bool `json:"flush,omitempty"`
	// Scroll lets a WIDE table (many columns) scroll HORIZONTALLY — each column keeps its
	// natural width and a scrollbar appears — instead of cramming every column into the
	// container so nothing feels squeezed. Set with HScroll.
	Scroll bool `json:"scroll,omitempty"`
}

TableData is what a Table view returns: the column names, the rows (each a slice of cells — plain scalars or rich cells like Badge/Link/Image), the total row count (for the pager; for a server table this is the full count, not len(Rows)), and optional hidden column names (kept in each row for row-detail/actions but not rendered). Build it with plugin.Table(...).

type TableOpt

type TableOpt func(*ViewDesc)

TableOpt configures an interactive Table view (row-click detail, row actions).

func DefaultSort

func DefaultSort(column, dir string) TableOpt

DefaultSort sets the sort a server table applies on first load (before the user touches a column header) — e.g. DefaultSort("indexed", "desc") for newest-first. dir is "asc" or "desc"; column must be one your handler's sort map accepts.

func Editable

func Editable(method string, columns ...string) TableOpt

Editable makes cells editable: editing one calls method with {row, column, value}. Pass column names to limit which are editable (none => every column).

func Facets

func Facets(facets ...Facet) TableOpt

Facets adds dropdown filters to a server table; the selected values arrive in the query as filters[key] (read with ReadTableQuery). Apply them in your store.

func HScroll added in v0.5.0

func HScroll() TableOpt

HScroll lets a wide table VIEW scroll horizontally (columns keep their natural width and a scrollbar appears) instead of cramming every column into the container. For an embedded CTable, set TableData.Scroll directly (or use CScroll).

func NoFilter

func NoFilter() TableOpt

NoFilter hides a table's search box — for a plain paged list with no user search.

func NoSort

func NoSort() TableOpt

NoSort makes a table's column headers non-interactive — the order is fixed by your handler (e.g. always newest-first), not user-sortable. Pair with NoFilter for a pure paged list.

func PageSize

func PageSize(n int) TableOpt

PageSize sets how many rows hope shows per page for this table (0 => hope default).

func RowActions

func RowActions(actions ...RowAction) TableOpt

RowActions adds per-row action buttons; each click calls the action's Method with {row: {column: value}}. Use for row-scoped mutations like "delete row".

func RowDetail

func RowDetail(method string) TableOpt

RowDetail makes table rows clickable: a click calls method with {row: {column: value}} and shows the returned kv/table in a modal (an author-controlled RPC).

func RowDetailButton

func RowDetailButton(method string) TableOpt

RowDetailButton is like RowDetail but triggers from a per-row button instead of a whole-row click — use it when the row is also inline-editable so the click to edit a cell and the click to open the detail don't collide.

func RowFlyout added in v0.0.8

func RowFlyout(method string) TableOpt

RowFlyout makes table rows clickable into a right-side DRAWER (not a modal): a click calls method with {row: {column: value}} and hope renders the returned component tree (plugin.Box(...) — image, key/values, Buttons bound to actions) in the flyout. Richer than RowDetail's modal; good for a compact record shown beside the table. If both RowFlyout and RowDetail are set, the flyout wins.

func RowFlyoutRefresh added in v0.5.3

func RowFlyoutRefresh(seconds int) TableOpt

RowFlyoutRefresh makes an OPEN row flyout re-invoke its method every `seconds` with the same row and re-render the returned tree in place (scroll preserved) — so a live drawer (a lifecycle Timeline, a progress view) follows along without the operator reopening it. 0 = one-shot (today's behavior). Pair with RowFlyout. Feature-gated: hope advertises "flyout-refresh" (check Caps(ctx).Supports); older hope ignores the option.

func RowFlyoutWidth added in v0.4.0

func RowFlyoutWidth(width string) TableOpt

RowFlyoutWidth sizes the row-flyout drawer: a preset ("small" | "large" | "xlarge") or an explicit px value ("680"). Empty = the default (~460px). Pair with RowFlyout.

func ServerSide

func ServerSide() TableOpt

ServerSide marks a table server-driven: hope sends the query state each call and expects one page + a total back, so a table too large to ship whole still works. Read the query with plugin.ReadTableQuery and return {columns, rows, total}.

type TableQuery

type TableQuery struct {
	Page     int               `json:"page"`
	PageSize int               `json:"page_size"`
	Sort     TableSort         `json:"sort"`
	Filter   string            `json:"filter"`
	Filters  map[string]string `json:"filters"`
}

TableQuery is hope's per-call query state for a ServerSide table: which page (0- based) of what size, an optional sort, the free-text filter, and any Facet selections (filters[key] = chosen value). Use it to run just that slice of your data and return {columns, rows, total}.

func ReadTableQuery

func ReadTableQuery(ctx context.Context) (q TableQuery, ok bool)

ReadTableQuery reads the server-table query state hope sends (under "_q"). ok is false when the call carried none (e.g. a non-server table) — treat that as page 0.

type TableSort

type TableSort struct {
	Column string `json:"column"`
	Dir    int    `json:"dir"`
}

TableSort is the sort request for a server-driven table: which column, and the direction (1 ascending, -1 descending; 0 = unsorted).

type TextData

type TextData struct {
	Text string `json:"text"`
}

TextData is what a Text view returns: a block of monospace text (logs, config, command output). A Text handler may also return a raw string.

type TipPos added in v0.0.4

type TipPos string

TipPos is where a tooltip points relative to its target.

const (
	TipTop       TipPos = "top" // default
	TipBottom    TipPos = "bottom"
	TipTopEnd    TipPos = "top-end"
	TipBottomEnd TipPos = "bottom-end"
)

type Tooltip added in v0.0.4

type Tooltip struct {
	Text string `json:"text"`
	Pos  TipPos `json:"pos,omitempty"`
}

Tooltip is hover help with an optional placement. Build one with Tip("text") or Tip("text", plugin.TipBottom). Empty Pos renders at the top.

func Tip added in v0.0.4

func Tip(text string, pos ...TipPos) *Tooltip

Tip builds a Tooltip — hover help text with an optional placement (the author's control over where it points): Tip("Reclaims space", plugin.TipBottom). Extra pos args are ignored.

type TreeData added in v0.0.5

type TreeData struct {
	Nodes []TreeNode `json:"nodes"`
}

TreeData is what a Tree view returns: a hierarchy of nodes. Return it (or a bare map literal) from a Tree handler.

type TreeNode added in v0.0.5

type TreeNode struct {
	Label     string     `json:"label"`
	Icon      string     `json:"icon,omitempty"` // built-in icon name or an Icons key
	Tone      string     `json:"tone,omitempty"` // ok|warn|bad|info dot
	To        string     `json:"to,omitempty"`   // plugin-relative nav target (like a Link cell)
	Collapsed bool       `json:"collapsed,omitempty"`
	Tip       *Tooltip   `json:"tip,omitempty"`
	Children  []TreeNode `json:"children,omitempty"`
	// Actions are per-node buttons (rename/delete/…), shown on hover — reuses RowAction, so
	// Icon/Danger/Fields/Tip all work. Clicking one runs its Method with Args as the {row}.
	Actions []RowAction    `json:"actions,omitempty"`
	Args    map[string]any `json:"args,omitempty"` // the {row} context passed to this node's Actions
}

TreeNode is one node in a Tree view. Children make it a collapsible group (Collapsed starts it closed). A non-empty To makes the label a plugin-relative link (like a Link/DetailLink cell), so a tree can navigate — e.g. To: "table/public.users". Icon and Tone add a leading icon and a status dot; Tip adds a hover tooltip. A node can be both a group and a link: the caret toggles children, the label navigates.

type ValidateFunc added in v0.5.0

type ValidateFunc func(ctx context.Context) ([]FieldError, error)

ValidateFunc validates the current form values (Params) and returns per-field errors (#4). hope calls it as the values change; an empty slice means the form is valid and Run enables.

type ViewDesc

type ViewDesc struct {
	Method string   `json:"method"`
	Label  string   `json:"label"`
	Kind   ViewKind `json:"kind"`
	Icon   string   `json:"icon,omitempty"`
	// Empty customizes the "no data" state (see EmptyState); unset => hope's generic text.
	Empty   *EmptyState `json:"empty,omitempty"`
	Lang    string      `json:"lang,omitempty"`    // query views: syntax-highlight language (sql, json, …)
	Default string      `json:"default,omitempty"` // query views: initial text; {param} placeholders are filled from the page param
	// RowMethod (table/query views): a method hope calls to open a row-detail modal,
	// with params {row: {column: value}}. The result (kv or table) is shown in a
	// modal — a fully author-controlled row-detail RPC.
	RowMethod string `json:"row_method,omitempty"`
	// RowFlyout (table/query views): like RowMethod but opens a right-side DRAWER instead
	// of a modal. The method receives {row} and returns a component tree hope renders in
	// the flyout. When both are set, the flyout wins.
	RowFlyout string `json:"row_flyout,omitempty"`
	// RowFlyoutWidth sizes the drawer: a preset ("small" | "large" | "xlarge") or an
	// explicit px value ("680"). Empty = the default (~460px).
	RowFlyoutWidth string `json:"row_flyout_width,omitempty"`
	// RowFlyoutRefresh re-invokes an OPEN flyout's method every N seconds (same row) and
	// swaps the body in place — a live drawer. 0 = one-shot (default). Set with RowFlyoutRefresh.
	RowFlyoutRefresh int `json:"row_flyout_refresh,omitempty"`
	// Scroll lets a WIDE table view (many columns) scroll HORIZONTALLY — columns keep their
	// natural width and a scrollbar appears — instead of cramming them into the container.
	// Set with HScroll.
	Scroll bool `json:"scroll,omitempty"`
	// --- Paged view (ViewKind Paged) ---
	// ItemTemplates maps an Item's Type -> a component template (B). Placeholders {field} in
	// the template's text/values are bound to the item's Data. Register with ItemTemplate.
	ItemTemplates map[string]*Comp `json:"item_templates,omitempty"`
	// Layout arranges a Paged view's items: "list" (default) | "grid" | "flow". Set with Layout.
	Layout string `json:"layout,omitempty"`
	// Infinite renders a Paged view with load-more-on-scroll instead of a pager. Set with Infinite.
	Infinite bool `json:"infinite,omitempty"`
	// RowDetailButton triggers RowMethod from a dedicated per-row button instead of a
	// whole-row click. Use when the row body is otherwise interactive (e.g. inline-
	// editable cells) so the two don't fight.
	RowDetailButton bool `json:"row_detail_button,omitempty"`
	// RowActions (table/query views): per-row action buttons. hope renders each in a
	// trailing column (and in the row-detail modal); clicking one calls its Method
	// with {row: {column: value}} — an author-controlled mutation like "delete row".
	// A Danger action is confirmed first; on success hope refetches the table.
	RowActions []RowAction `json:"row_actions,omitempty"`
	// PageSize (table/query views) sets how many rows hope shows per page — the
	// author knows the shape of their data, so paging is plugin-level. 0 => hope's
	// default.
	PageSize int `json:"page_size,omitempty"`
	// EditMethod (table/query views): editing a cell calls this method with
	// {row: {column: value}, column, value}. Return ok (and optionally a message).
	// EditColumns limits which columns are editable (empty => all). An
	// author-controlled inline-edit RPC — hope refetches the table on success.
	EditMethod  string   `json:"edit_method,omitempty"`
	EditColumns []string `json:"edit_columns,omitempty"`
	// Server marks a table server-driven: hope does NOT ship every row and page in
	// the browser. Instead it sends the query state ({_q: {page, page_size, sort,
	// filter}}) on each call and expects one page back plus a total row count
	// ({columns, rows, total}). Required for tables too large to send whole — read
	// the query with plugin.ReadTableQuery.
	Server bool `json:"server,omitempty"`
	// Refresh adds a manual refresh button to the view header that re-fetches it —
	// e.g. a stat/counter you want to recompute on demand.
	Refresh bool `json:"refresh,omitempty"`
	// RefreshInterval auto-refetches the view every N seconds (0 = off) — a live-ish
	// view without a stream. hope stops the timer when the view leaves the DOM.
	RefreshInterval int `json:"refresh_interval,omitempty"`
	// Static marks a view's data as fixed for the life of the surface: hope fetches it
	// once and serves the cached result on tab re-entry / re-navigation instead of
	// re-calling the plugin — cutting round-trips and easing the per-plugin rate limit.
	// A manual Refresh() button still forces a re-fetch, so Static()+Refresh() = "load
	// once, refresh on demand". Don't combine with RefreshInterval.
	Static bool `json:"static,omitempty"`
	// Facets (server tables) are dropdown filters hope renders in the toolbar; the
	// selected values arrive in the query as filters[key] (see TableQuery.Filters),
	// which you apply in your store. Distinct from the free-text search box.
	Facets []Facet `json:"facets,omitempty"`
	// DefaultSort (server tables) is the sort hope applies on FIRST load, before the
	// user clicks any column header — e.g. newest-indexed first. hope seeds the query
	// state with it (so it arrives in TableQuery.Sort) and shows the arrow on that
	// column. The user can still re-sort. Column must be one your handler accepts.
	DefaultSort *SortSpec `json:"default_sort,omitempty"`
	// NoFilter hides the search box and NoSort makes column headers non-interactive —
	// for a plain paged list (a fixed order your handler controls, no user search/sort).
	NoFilter bool `json:"no_filter,omitempty"`
	NoSort   bool `json:"no_sort,omitempty"`
	// --- Graph view (ViewKind Graph) — all optional method refs; a bare GraphView is just
	// the canvas. Set with the Graph* opts. hope calls each mutation with {row:{...}} (like a
	// RowAction) and re-fetches the graph on success. ---
	GraphMove            string     `json:"graph_move,omitempty"`             // persist a dragged node's position {id,x,y}
	GraphConnect         string     `json:"graph_connect,omitempty"`          // add an edge {from:"n:p", to:"n:p"}
	GraphDisconnect      string     `json:"graph_disconnect,omitempty"`       // remove an edge {from,to}
	GraphDelete          string     `json:"graph_delete,omitempty"`           // delete a node {id}
	GraphAdd             string     `json:"graph_add,omitempty"`              // create a node {type,x,y} (palette drop / dbl-click)
	GraphNodeFlyout      string     `json:"graph_node_flyout,omitempty"`      // node click -> a Comp body in the drawer {id}
	GraphConfig          string     `json:"graph_config,omitempty"`           // persist a node's config form: {id, ...field values}
	GraphMenu            string     `json:"graph_menu,omitempty"`             // right-click -> []GraphMenuItem for {kind, id/from/to}
	GraphValidateConnect string     `json:"graph_validate_connect,omitempty"` // optional pre-connect gate {from,to} -> {ok,reason}
	GraphRun             string     `json:"graph_run,omitempty"`              // a StreamComponent-kind stream emitting GraphData frames (run highlight)
	GraphSidebar         string     `json:"graph_sidebar,omitempty"`          // left rail: a Comp (browse the plugin's DAGs) {}
	GraphSidebarActions  []string   `json:"graph_sidebar_actions,omitempty"`  // action-method refs rendered as buttons atop the sidebar (New, ...)
	GraphToolbar         string     `json:"graph_toolbar,omitempty"`          // top strip: a Comp (title/run/filters) {}
	GraphSelect          string     `json:"graph_select,omitempty"`           // sidebar row click -> set active graph {id}; canvas refetches with {graph:id}
	GraphPalette         string     `json:"graph_palette,omitempty"`          // the node-TYPE catalog: returns []NodeType
	GraphTypeInfo        string     `json:"graph_type_info,omitempty"`        // palette info button -> a Comp about a node TYPE {type} (view before adding)
	Palette              []NodeType `json:"palette,omitempty"`                // a STATIC node-type catalog (alternative to GraphPalette)
	GraphDirected        bool       `json:"graph_directed,omitempty"`         // draw arrowheads on edges
	GraphSnap            int        `json:"graph_snap,omitempty"`             // snap dragged nodes to an N-px grid (0 = free)
}

ViewDesc describes a read-only data view and how to render it.

type ViewFunc

type ViewFunc func(ctx context.Context) (any, error)

ViewFunc returns the data for a view; hope renders it per the view's ViewKind. For a Query view, the user's input arrives in the request params — read it with plugin.Input(ctx) (or plugin.Params for structured input).

type ViewKind

type ViewKind string

ViewKind tells hope how to render a view's returned data.

const (
	KV    ViewKind = "kv"    // flat key/value map -> hope-kvlist
	Table ViewKind = "table" // {columns, rows} -> paginated grid
	Query ViewKind = "query" // user-edited input -> tabular result grid (e.g. a SQL box)
	Tree  ViewKind = "tree"  // hierarchy -> tree browser (e.g. a schema)
	Chart ViewKind = "chart" // {type, labels, series} -> bar/line chart (see ChartData)
	Cards ViewKind = "cards" // {items:[Card]} -> a responsive grid of cards (a gallery)
	Stat  ViewKind = "stat"  // {stats:[Stat]} (or one Stat) -> big-number stat blocks (see StatData)
	Text  ViewKind = "text"  // {text:"…"} (or a raw string) -> a monospace scrollable block (logs, config, output)
	Paged ViewKind = "paged" // a Page{Items,...} -> a server-paged collection; each item renders via its Comp (A) or a per-type ItemTemplate (B). Layout list|grid|flow.
	// Search is an autocomplete box: hope calls the method with {q: <typed text>} as the
	// user types (debounced) and renders the returned SearchItems as a live dropdown.
	// Selecting one navigates to its To (a DetailPage/link target) — a "go to X" jump.
	Search ViewKind = "search"
	// CompView is the escape hatch: the handler returns a *Comp — a tree of safe
	// primitives (box/row/heading/keyval/sparkline/cell/…) hope composes into a custom
	// widget the built-in kinds don't cover. See component.go. Also usable inline in a
	// layout via plugin.Component (no per-view round-trip).
	CompView ViewKind = "component"
	// Graph is the spatial / blueprint surface: the handler returns a GraphData of nodes at
	// x/y with edges between ports. hope ships NO node types — the plugin defines each node's
	// title/ports/body (an arbitrary Comp) and owns persistence + execution; hope is the
	// editor (pan/zoom, drag, connect) and calls the plugin's mutation methods, then re-fetches.
	// See GraphData + p.GraphView.
	Graph ViewKind = "graph"
)

type ViewOpt

type ViewOpt = TableOpt

ViewOpt configures a view (Refreshable, PageSize on tables, …). TableOpt is the same underlying type, so table options compose here too.

func EmptyView added in v0.0.6

func EmptyView(title string, opts ...EmptyOpt) ViewOpt

EmptyView customizes the "no data" state shown when a view resolves empty (an empty table, a stat with no blocks) instead of hope's generic text — a title plus optional icon/text (or a custom Comp). Attach it wherever view opts are accepted (TableView, StatView, ComponentView), e.g.

p.TableView("slow", "Slow queries", fn,
    plugin.EmptyView("No slow queries 🎉", plugin.EmptyIcon("check")))

func Infinite added in v0.5.0

func Infinite() ViewOpt

Infinite renders a Paged view with load-more-on-scroll instead of a pager.

func ItemTemplate added in v0.5.0

func ItemTemplate(typ string, tmpl *Comp) ViewOpt

ItemTemplate registers a per-Type component template for a Paged view (B): hope binds an item's Data into it, substituting {field} placeholders in text/values. Register one per item Type; an item that instead carries its own Comp (A) uses that; an item with neither falls back to a key/value render.

func PageLayout added in v0.5.0

func PageLayout(l string) ViewOpt

PageLayout arranges a Paged view's items: "list" (default) | "grid" | "flow".

func RefreshEvery

func RefreshEvery(seconds int) ViewOpt

RefreshEvery auto-refetches the view every n seconds (a live-ish view without a stream). hope stops the timer when the view leaves the DOM.

func Refreshable

func Refreshable() ViewOpt

Refreshable adds a manual refresh button to the view header (re-fetches on click).

func Static added in v0.0.6

func Static() ViewOpt

Static marks a view's data fixed for the life of the surface: hope fetches it once and reuses the cached result on tab re-entry / re-navigation instead of re-calling the plugin — fewer round-trips, less rate-limit pressure. Pair with Refreshable() for a "load once, refresh on demand" view. Don't combine with RefreshEvery.

type WizardStep added in v0.5.0

type WizardStep struct {
	Title  string  `json:"title,omitempty"`
	Hint   string  `json:"hint,omitempty"`
	Fields []Field `json:"fields"`
}

WizardStep is one page of a stepped action form — a title (+ optional hint) over a set of fields the modal renders on its own step. Build with Step / StepHint.

func Step added in v0.5.0

func Step(title string, fields ...Field) WizardStep

Step builds one wizard step: a titled set of fields. Pass steps to ActionSteps.

func StepHint added in v0.5.0

func StepHint(title, hint string, fields ...Field) WizardStep

StepHint is Step with a helper line shown under the step title.

Jump to

Keyboard shortcuts

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