boff

package
v2.10.28 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package boff holds the block-based rendering machinery used by backoffice pages. A page is a slice of Block; each block renders to a fragment of HTML, and the blocks are concatenated top to bottom into a page shell. This is the generic core, free of any particular page's models, so it can back a history ledger, an overview list, or any other backoffice page.

Index

Constants

View Source
const (
	RoleAdmin = "admin"
	RoleWrite = "write"
	RoleRead  = "read"
)
View Source
const PageParam = "page"

PageParam is the query parameter the overview pager pages with.

Variables

This section is empty.

Functions

func GateSlice

func GateSlice[T HasRequiredRole](rc RenderContext, values []T) []T

GateSlice returns the entries rc's viewer may see, dropping the rest. Returns nil when nothing survives, so an empty block renders nothing.

func HighlightJSON added in v2.10.2

func HighlightJSON(payload string) string

HighlightJSON colourises an already-indented JSON payload with Bootstrap text-colour utility classes, escaping it first. Those classes come from the stylesheet the page already has - server-side because a client-side highlighter would be dropped together with the <head> when the page is embedded as a backoffice fragment. Shared by every block that shows a JSON payload verbatim (a ledger record, a summary row) so the rendering stays consistent across backoffice pages.

func IsAmount added in v2.10.28

func IsAmount(value string) bool

IsAmount reports whether value is a rendered money amount.

func IsId added in v2.10.28

func IsId(value string) bool

IsId reports whether value looks like an identifier - used to decide whether a displayed value is shortened and made copyable rather than printed in full. Deliberately conservative: anything with whitespace or punctuation, and anything short enough to be a human-readable name, is prose.

func IsZeroAmount added in v2.10.28

func IsZeroAmount(value string) bool

IsZeroAmount reports whether value is a zero amount, which the page mutes: a zero discount is information an operator scans past, not one they read.

func JSONFieldCount added in v2.10.28

func JSONFieldCount(payload string) int

JSONFieldCount is how many fields a payload carries at its top level, the number the payload chip shows ("{ } 5"). An array counts its elements. A payload that is neither (a bare string, invalid JSON, or empty) counts zero, which is what suppresses the chip.

func MayPerform

func MayPerform(viewer *jwt.Identity, required Role) bool

MayPerform reports whether viewer satisfies required. An empty required is ungated; no viewer denies everything gated.

ponytail: a role qualified with a foreign audience is denied unless the viewer was issued for that audience. Cross-audience display via the advisory Actor-Roles header is only worth building when a page actually mixes services.

func MustTemplatesFromFS

func MustTemplatesFromFS(fsys fs.FS) *template.Template

MustTemplatesFromFS is TemplatesFromFS for a package-level var: it panics on a parse error, since an embedded template that fails to parse is a build-time bug, not a runtime condition.

func Render

func Render(w io.Writer, cfg RenderConfig) error

Render renders cfg.Blocks and writes the full page shell to w, using the built-in shell. Each block renders with a RenderContext carrying cfg.Viewer, so blocks gate themselves. Use RenderWithShell to supply your own shell template (from Templates or TemplatesFromFS) carrying extra funcs or sub-templates.

func RenderOverview

func RenderOverview(w io.Writer, title string, headers []string, rows []OverviewRow) error

RenderOverview writes a standalone clickable table; each row links to Link.

func RenderOverviewWithConfig

func RenderOverviewWithConfig(w io.Writer, cfg OverviewConfig) error

RenderOverviewWithConfig is RenderOverview with the optional display elements (filter form, scope note).

func RenderTemplate

func RenderTemplate(rc RenderContext, tmpl *template.Template, name string, data any) (template.HTML, error)

RenderTemplate executes the named (sub-)template of tmpl with data, bound to rc, and returns its HTML. It is the plumbing behind TemplateBlock and the way to render a template from your own Block: the template can render a child Block inline via {{ . | render }}, which executes against the same rc. tmpl is cloned per call so the binding never leaks into a shared template, which also means tmpl itself is never executed and stays reusable.

func RenderWithShell

func RenderWithShell(w io.Writer, tpl *template.Template, cfg RenderConfig) error

RenderWithShell is Render with an explicit shell template, from Templates or TemplatesFromFS. tpl is never executed directly - RenderTemplate executes a render-bound clone so tpl stays pristine and reusable.

func ShortId added in v2.10.28

func ShortId(value string) string

ShortId renders a long identifier as its first six and last six characters, e.g. "01M1ET…X23ZQW". A "type:" prefix (as a history GroupId or a locally generated request id carries) is kept intact and only the id after it is shortened, so "local:01M1…" still reads as a local id. Short values are returned unchanged.

The full value belongs in a title attribute next to it - the display form is lossy on purpose, the page must still let an operator copy the whole thing.

func ShortIdN added in v2.10.28

func ShortIdN(value string, head, tail int) string

ShortIdN is ShortId with an explicit head and tail length, for ids with another shape than a ULID - a 32 character trace id reads better as 8 and 6.

func ShortIdsIn added in v2.10.28

func ShortIdsIn(text string) string

ShortIdsIn shortens every id embedded in text, leaving the rest alone - for a label or a sentence that carries an id inside it. The full text belongs in a title attribute next to it.

func Templates

func Templates() *template.Template

Templates returns a fresh page template carrying the shared default funcs (formatTime, add, formatMoney, shortId, pipClass, statusClass, payload, render) and the shared console partials (boff/id, boff/payload-chip, boff/payload-panel), so a page's own blocks render a copyable id or a collapsible payload exactly like the built-in ones. Parse your own block sub-templates into it, then pass it to RenderWithShell. It is a fresh template every call, so callers never share (or accidentally execute) a common base.

func TemplatesFromFS

func TemplatesFromFS(fsys fs.FS) (*template.Template, error)

TemplatesFromFS is Templates with every .gohtml file in fsys parsed in, for a page that keeps its block templates in an embedded FS. It walks fsys, so the files may sit at any depth (a templates/ subdirectory, say) without the caller naming a glob. The parsed definitions and the shared funcs are all available to RenderWithShell.

func UpdateNavigation added in v2.10.2

func UpdateNavigation(c *echo.Context, links []NavLink)

UpdateNavigation writes links to the navigation cookie as the persistent top-level navigation, replacing whatever was stored before.

func ViewerOf

func ViewerOf(ctx context.Context, override *jwt.Identity) *jwt.Identity

ViewerOf returns the identity used for gating: the explicit override, else the verified identity of the request, else nil.

Types

type Action

type Action struct {
	Description    string // e.g. "Cancel item Sword-Pack"
	ButtonText     string // e.g. "Cancel"
	Endpoint       string // e.g. "/orders/backoffice/v1/orders/123/items/1/cancel"
	ConfirmMessage string // optional confirmation prompt; empty = submit immediately
	ConfirmText    string // label of the modal's confirm button; empty = "OK"
	StatusText     string // non-empty = show label instead of button

	// true renders a link to Endpoint instead of a form, for an action that only
	// navigates. Ignores ConfirmMessage.
	Link bool

	// RequiredRole gates the whole action: "write" or "admin" means that role on
	// this service's own audience, "payment-service:admin" names another
	// audience. Empty means always shown. A viewer who may not perform the action
	// does not see it at all - a disabled button still tells a read-only user
	// which endpoint to curl.
	RequiredRole Role
}

Action is one row in the actions table on a backoffice page. When StatusText is non-empty the row renders a static label instead of a button (e.g. "Cancelled"); otherwise a plain HTML form that POSTs to Endpoint.

An action needs no JavaScript of its own: the form is a form, and a ConfirmMessage renders a Bootstrap modal driven by Bootstrap's own JS. (The page does carry one small shared console script for payload toggles and copy-to-clipboard, but nothing an action depends on.) Endpoint must therefore be a URL the browser can resolve - a service behind backoffice builds it from the base path backoffice sends with the fragment request, not from its own internal path.

func (Action) GatingRole

func (a Action) GatingRole() Role

GatingRole implements HasRequiredRole.

type ActionsBlock

type ActionsBlock []Action

ActionsBlock is an Action list rendered as the actions table. It gates itself at render time: the actions a viewer may not perform are dropped. Renders nothing when empty.

The slice is the block: boff.ActionsBlock{...} or a boff.ActionsBlock(actions) conversion both give you a Block.

func (ActionsBlock) Render

func (b ActionsBlock) Render(rc RenderContext) (template.HTML, error)

type Block

type Block interface {
	Render(rc RenderContext) (template.HTML, error)
}

Block is one renderable section of a page. Blocks are rendered top to bottom in the order given, so a page is just a slice of them. Providing your own Block is how you extend a page beyond its built-in sections without touching the page shell.

Render receives a RenderContext carrying the viewing identity and the shell template. A block gates itself here - it consults rc.May and emits only what the viewer may see. Because the context is passed on every call, a container block hands the same rc to its children, so gating and template resolution compose to any nesting depth.

A block is free to produce any HTML it likes. An empty block returns no bytes and is simply skipped.

func Blocks

func Blocks(children ...Block) Block

Blocks renders a sequence of child blocks and concatenates their output into one HTML fragment, so a container block can hold other blocks - and so a page turns its whole block slice into markup. Empty children (those that render to nothing) are skipped, and the same rc is passed to each, so gating reaches nested blocks unchanged.

func FilterableTableBlock added in v2.10.2

func FilterableTableBlock(filters []OverviewFilter, headers []string, rows []OverviewRow) Block

FilterableTableBlock renders the filter form and the results table together in one card, the form separated from the table by a divider. This is what RenderOverviewWithConfig uses by default. Renders the table even when filters is empty (the form section is simply omitted).

func FiltersBlock

func FiltersBlock(filters []OverviewFilter) Block

FiltersBlock renders the GET filter form on its own, outside any card. Reach for FilterableTableBlock instead when the filters belong above a results table - the common case, and the default overview layout. Renders nothing when filters is empty.

func Gate

func Gate(required Role, block Block) Block

Gate wraps a block so it renders only when the viewer satisfies required, in the notation of Action.RequiredRole. A denied viewer sees nothing - the whole wrapped block vanishes. This is the coarse counterpart to the fine-grained, per-item gating SummaryBlock and ActionsBlock do: reach for it to hide an entire section (a card, a whole custom block) behind one role.

func PagerBlock

func PagerBlock(m PagerModel) Block

PagerBlock renders the pagination nav. Renders nothing when there is no previous or next page.

func ScopeNoteBlock

func ScopeNoteBlock(note string) Block

ScopeNoteBlock renders the one-line scope note. Renders nothing when empty.

func SummaryCard added in v2.10.2

func SummaryCard(title string, items []SummaryItem) Block

SummaryCard is SummaryBlock under a card title, for a page that shows more than one summary section (e.g. account data next to profile data) and needs each one labeled. It gates and renders nothing exactly like SummaryBlock.

func TableBlock

func TableBlock(headers []string, rows []OverviewRow) Block

TableBlock renders the clickable rows table in its own card. Always renders (shows a "No records." row when rows is empty). Use this for a table with no filter form (e.g. a fixed recent-N list); use FilterableTableBlock when the table has a filter form above it, so both sit in one card.

type BlockFunc

type BlockFunc func(rc RenderContext) (template.HTML, error)

BlockFunc adapts a plain function to a Block, so a one-off block needs no named type.

func (BlockFunc) Render

func (f BlockFunc) Render(rc RenderContext) (template.HTML, error)
type Breadcrumb struct {
	Key   string `json:"k"`
	Label string `json:"l"`
	Path  string `json:"p"`
}

Breadcrumb is a single entry in the cross-service navigation trail.

Key is a stable logical identifier for the resource shown on the page, e.g. "player:abcdef" or "order:123". It is used to look up an entry across services, independent of the actual URL structure.

Label and Path are only known to (and only ever set by) the service that owns the resource identified by Key. Other services may reference the same Key without knowing Label or Path.

func BreadcrumbLocal(key, label, path string) Breadcrumb

BreadcrumbLocal creates a Breadcrumb for a resource owned by the calling service, with an authoritative label and path.

func BreadcrumbRemote(key string) Breadcrumb

BreadcrumbRemote creates a Breadcrumb for an ancestor resource whose label and path are not known to the calling service. It will be rendered without a link unless a cached entry with the same Key is already known.

func BreadcrumbRemoteWithLabel(key, label string) Breadcrumb

BreadcrumbRemoteWithLabel creates a Breadcrumb for an ancestor resource whose label is known to the calling service, but whose path is not. It will be rendered without a link unless a cached entry with the same Key is already known. BreadcrumbRemote should be prefered.

func ReconcileBreadcrumbs added in v2.10.2

func ReconcileBreadcrumbs(c *echo.Context, entries ...Breadcrumb) []Breadcrumb

ReconcileBreadcrumbs loads the current breadcrumb cookie, reconciles it with the given breadcrumb chain (see BreadcrumbCookie.Reconcile), saves the result back to the cookie, and returns the reconciled breadcrumbs.

type BreadcrumbsBlock []Breadcrumb

BreadcrumbsBlock renders the breadcrumb trail as a bootstrap breadcrumb.

type CardBlock

type CardBlock struct {
	Title    string
	Subtitle string
	Body     Block
	// Raised adds a small drop shadow (Bootstrap shadow-sm) to lift the card off
	// the page.
	Raised bool
}

CardBlock wraps a body block in a Bootstrap card with a title and optional subtitle. The body is itself a Block, rendered inline by the card template via the render func (RenderTemplate), with the same rc - so gating and nested blocks compose inside a card like anywhere else.

func (CardBlock) Render

func (b CardBlock) Render(rc RenderContext) (template.HTML, error)

type DefaultOverviewBlocks

type DefaultOverviewBlocks struct {
	Header    Block
	Filters   Block
	ScopeNote Block
	Pager     Block
	Table     Block
}

DefaultOverviewBlocks are the built-in blocks an overview page renders out of the box, handed to an OverviewConfig.Blocks callback so custom layouts can reuse them. Pager is shared by the top and bottom pager. Filters is the form on its own (not part of All()) for a custom layout that wants it apart from Table, which by default already renders the filters and the table together.

func (DefaultOverviewBlocks) All

func (d DefaultOverviewBlocks) All() []Block

All returns the default blocks in their default order (header, scope note, pager, filters+table, pager).

type FilterOption

type FilterOption struct {
	Value string
	Label string
}

FilterOption is one entry of an OverviewFilter dropdown.

type HTMLBlock

type HTMLBlock template.HTML

HTMLBlock is a block of pre-rendered HTML, for callers that want to drop in arbitrary markup without a template.

func (HTMLBlock) Render

func (b HTMLBlock) Render(RenderContext) (template.HTML, error)

type HasRequiredRole

type HasRequiredRole interface {
	GatingRole() Role
}

HasRequiredRole is implemented by anything a viewer can be gated against - an Action, a NavLink. GateSlice uses it to drop the entries a viewer may not see, so a new gated element type only has to report its role, not its own filter.

type HeaderBlock

type HeaderBlock struct {
	Title        string
	Subtitle     string
	ErrorMessage string
}

HeaderBlock is the page heading: an h1 title, an optional monospace subtitle, and an optional error alert. It was the shell's built-in header; as a block a page places it wherever it likes (or drops it), for full control of the page layout.

func (HeaderBlock) Render

func (b HeaderBlock) Render(rc RenderContext) (template.HTML, error)

type MetaPart added in v2.10.28

type MetaPart struct {
	Text string
	Link string
}

MetaPart is one segment of a SummaryRow's meta line: plain text, or a link when Link is set.

type NavLink struct {
	Label        string `json:"l"`
	Path         string `json:"p"`
	RequiredRole Role   `json:"r"`

	// IsActive is set by LoadNavigation when Path is a prefix of the
	// current request path. It is never persisted in the cookie.
	IsActive bool `json:"-"`
}

NavLink is a persistent, top-level navigation link that, unlike a Breadcrumb, is not part of the current drill-down trail. It is stored alongside the breadcrumb trail so it survives across services too.

func LoadNavigation added in v2.10.2

func LoadNavigation(c *echo.Context) []NavLink

LoadNavigation returns the persistent top-level navigation links stored in the navigation cookie. It returns nil if the cookie is missing or could not be decoded.

type NavigationBlock []NavLink

NavigationBlock renders the persistent navigation links as a bootstrap nav bar.

type OverviewCell

type OverviewCell struct {
	Text string
	Link string
	Tone string
}

OverviewCell is one table cell with its optional own link.

type OverviewConfig

type OverviewConfig struct {
	Title     string
	Headers   []string
	Rows      []OverviewRow
	Filters   []OverviewFilter
	ScopeNote string

	// Page is the 1-based page number shown; 0 and 1 both mean the first page.
	// HasNext tells the pager that another page exists - the caller knows this by
	// loading one row more than it displays, so no count query is needed.
	Page    int
	HasNext bool
	// TotalPages enables the jump to the last page. Leave it 0 when counting the
	// whole result set is not worth a second query; the pager then only walks.
	TotalPages int

	// Blocks overrides the sections rendered on the page. When nil the page uses
	// its default layout: a HeaderBlock, ScopeNoteBlock, a PagerBlock, a
	// FilterableTableBlock (filters and table in one card) and a trailing
	// PagerBlock built from the fields above. When set, the callback receives
	// those default blocks and returns the blocks to render in order - so a
	// caller can reorder them, drop one, or splice its own Block in.
	Blocks func(defaults DefaultOverviewBlocks) []Block
}

OverviewConfig bundles everything the overview page renders.

type OverviewFilter

type OverviewFilter struct {
	Label       string
	Name        string
	Value       string
	Placeholder string
	// Type is the HTML input type; empty means "text". "date" gets the browser's
	// own date picker, no JavaScript needed.
	Type string
	// Options, when non-empty, renders a <select> instead of an input. The empty
	// value must be part of the list to allow "no filter".
	Options []FilterOption
	// Hidden keeps the value in the form and in the pagination links without
	// showing a control - for a filter the page receives from elsewhere (a deep
	// link carrying a player id) rather than one the viewer types.
	Hidden bool
}

OverviewFilter is one field of the overview filter form. Value is the currently applied value, echoed back so the form stays sticky.

func (OverviewFilter) InputType

func (f OverviewFilter) InputType() string

InputType is the type attribute of a text-ish filter input.

type OverviewRow

type OverviewRow struct {
	Link      string
	Cells     []string
	CellLinks []string
	// CellTones renders individual cells as a bootstrap badge in that tone
	// (index-aligned with Cells, empty entry = plain text) - for a state column an
	// operator scans down rather than reads, e.g. a status. A cell link wins over
	// a tone.
	CellTones []string
}

OverviewRow is one list entry; Cells aligns with the overview Headers.

Link makes the whole row navigate to one detail page. CellLinks instead links individual cells (index-aligned with Cells, empty entry = plain text), which is what a table with several targets needs - an id column pointing at this service's detail page, a foreign id pointing at the owning service. A cell link is rendered as a visible link; a row link stays inconspicuous.

func (OverviewRow) CellAt

func (r OverviewRow) CellAt(i int) OverviewCell

CellAt pairs a cell with its own link, so the template does not have to index two slices in parallel.

type PagerModel

type PagerModel struct {
	Page       int
	TotalPages int
	FirstLink  string
	PrevLink   string
	NextLink   string
	LastLink   string
}

PagerModel is the pagination state a PagerBlock renders. Both pagers (above and below the table) render the same model. The Link fields are empty when there is no such page.

type PanelBlock added in v2.10.2

type PanelBlock struct {
	Children []Block
	// Raised adds a small drop shadow (Bootstrap shadow-sm) to lift the card off
	// the page.
	Raised bool
}

PanelBlock wraps child blocks in a bare Bootstrap card frame, one that adds no card-body padding of its own - unlike CardBlock, whose Body always sits in a padded card-body. Reach for PanelBlock when a card holds sections with different padding needs (e.g. a padded filter form above a table that must sit flush against the card's edges); each child renders itself and is responsible for its own spacing. A child that renders to nothing (Skip, or an empty Blocks) simply contributes nothing.

func (PanelBlock) Render added in v2.10.2

func (b PanelBlock) Render(rc RenderContext) (template.HTML, error)

type RenderConfig

type RenderConfig struct {
	Title  string
	Viewer *jwt.Identity
	Blocks []Block
}

RenderConfig is the caller-supplied config for the shell. Title feeds the document <title>; the visible page heading is a HeaderBlock a page adds to Blocks. Blocks are rendered into the shell body in order.

Viewer is the identity the page's gated blocks filter themselves against. It is put into the RenderContext each block renders with. Leave it nil to fail closed - every gated element is then hidden. Use ViewerOf to derive it from the request context.

type RenderContext

type RenderContext struct {
	// Viewer is the identity gated blocks filter themselves against. Nil means
	// fail closed: May reports false for everything gated.
	Viewer *jwt.Identity
}

RenderContext carries everything a Block needs while it renders - currently the viewing identity to gate against. It is passed to every Block.Render, including nested ones: a container block hands its own rc straight to its children, so gating reaches any depth.

It is a struct (not a bare *jwt.Identity) so more render-time values can be propagated later - a base path for URL building, a nonce, feature flags - without churning the Block interface again.

func (RenderContext) May

func (rc RenderContext) May(required Role) bool

May reports whether the context's viewer satisfies required, in the notation of Action.RequiredRole ("write", "admin", or "audience:role"). An empty required is ungated and always allowed; a nil viewer denies everything gated.

This is the one check both Go blocks and the "may" template function share, so a block's Render and its template agree on who may see what. Reuse it when you write your own block instead of reaching for jwt directly.

type Role

type Role string

Role names the permission a gated element requires, in the notation the whole package shares: "write" or "admin" means that role on this service's own audience; "payment-service:admin" names a role on a foreign audience. The empty Role is ungated - always shown.

func RoleOf

func RoleOf(audience, role string) Role

type SummaryBlock

type SummaryBlock []SummaryItem

SummaryBlock is a SummaryItem list rendered as the current-state summary card. It gates itself at render time: the links a viewer may not follow are demoted to plain values. Renders nothing when empty.

The slice is the block: boff.SummaryBlock{...} or a boff.SummaryBlock(items) conversion both give you a Block. Use SummaryCard instead for a titled card.

func (SummaryBlock) Render

func (b SummaryBlock) Render(rc RenderContext) (template.HTML, error)

type SummaryItem

type SummaryItem struct {
	Label string
	Value string
	// Link, when non-empty, renders Value as an <a href> to this URL instead of
	// plain text - e.g. a cross-service backoffice link to the page that owns the
	// referenced entity (a payment or draw history page behind the api-gateway).
	Link string

	// RequiredRole gates the Link only, in the same notation as
	// Action.RequiredRole. A denied item still shows Label and Value, just not as
	// an anchor: the value itself is not the secret, the page behind it is.
	RequiredRole Role

	// JSON, when non-empty, renders Value as a collapsible <details> whose body is
	// this payload, highlighted like a ledger record - for a row that summarises a
	// structure the operator sometimes needs verbatim (an order item as stored).
	JSON string

	// Tone, when non-empty, renders Value with a severity pip in that tone
	// ("success", "danger", "warning", ... in the Bootstrap naming) - for a state
	// value an operator scans for rather than reads, e.g. an order status. A tone
	// outside the severity vocabulary renders a neutral pip. Ignored for a JSON
	// row.
	Tone string

	// Row, when set, renders this item as a full-width two-line row below the
	// facts instead of one label/value cell - for an entry that is a small record
	// of its own (a line item, a child object) rather than a single value. Value
	// is then unused; JSON, when set, still renders as this row's payload.
	Row *SummaryRow
}

SummaryItem is one label/value row shown above the page content, describing the current state of the tracked object. Ordered slice (not a map) so the page renders stably.

func (SummaryItem) FieldCount added in v2.10.28

func (i SummaryItem) FieldCount() int

FieldCount is how many fields the item's JSON payload carries, i.e. what the payload chip shows. Zero means no chip.

func (SummaryItem) IsId added in v2.10.28

func (i SummaryItem) IsId() bool

IsId reports whether Value looks like an identifier, and is therefore rendered shortened and copyable rather than in full.

func (SummaryItem) JSONHTML added in v2.10.2

func (i SummaryItem) JSONHTML() template.HTML

JSONHTML is SummaryItem.JSON colourised, same treatment as a record payload.

func (SummaryItem) LabelShortened added in v2.10.28

func (i SummaryItem) LabelShortened() bool

LabelShortened reports whether ShortLabel dropped anything, i.e. whether the label needs a title attribute carrying the full text.

func (SummaryItem) Mono added in v2.10.28

func (i SummaryItem) Mono() bool

Mono reports whether Value renders in the monospace, tabular-figure style - true for identifiers and money amounts, the values that are compared character by character or read as a column.

func (SummaryItem) Muted added in v2.10.28

func (i SummaryItem) Muted() bool

Muted reports whether Value is displayed muted: a zero amount is information an operator scans past rather than reads.

func (SummaryItem) ShortLabel added in v2.10.28

func (i SummaryItem) ShortLabel() string

ShortLabel is Label with any id embedded in it shortened, so a label like "Draw 01M1DTGTM47SC9XFPEBAJRRD65" does not wrap across three lines of a grid column.

type SummaryRow added in v2.10.28

type SummaryRow struct {
	// Tone is the severity of the row as a whole, shown as the leading pip, in
	// the same naming as SummaryItem.Tone. Empty is neutral.
	Tone string

	// Title is the primary text, e.g. the name of the thing.
	Title string

	// Subtitle is secondary text on the same line, e.g. a quantity and a price.
	Subtitle string

	// Tags render as small outline badges after the subtitle, e.g. a type or a
	// category.
	Tags []string

	// State is a trailing state label with its own pip, e.g. a lifecycle state.
	// StateTone gives it a severity, in the same naming as SummaryItem.Tone.
	State     string
	StateTone string

	// Meta is the second line, rendered small and muted with its parts separated
	// by a middot.
	Meta []MetaPart
}

SummaryRow is the structured form of a SummaryItem that describes a whole small record. It is deliberately generic - a title, a subtitle, tags, a state and a meta line - so any page can express its own kind of line item without this package knowing what the item is.

type TemplateBlock

type TemplateBlock struct {
	Name     string
	Model    any
	Skip     bool
	Template *template.Template
}

TemplateBlock renders a named (sub-)template with a model. It is the common shape of the built-in blocks and the easiest way to add your own: give it the Name of a template and a Model to execute it with.

Template must contain a definition for Name. The built-in blocks set it to the package shell, so they resolve the sub-templates defined alongside the shell. Set Skip to render nothing, which is how a section vanishes when it has no content.

func (TemplateBlock) Render

func (b TemplateBlock) Render(rc RenderContext) (template.HTML, error)

Jump to

Keyboard shortcuts

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