Documentation
¶
Overview ¶
Package guideflow implements the cinematic `bonsai guide` viewer — a tabbed BubbleTea scroll viewport that renders bundled markdown guides through glamour inside the shared initflow chrome (header + footer + min-size floor). The package is consumed by cmd/guide.go on TTY invocations; non-TTY (piped output) falls back to a static one-shot glamour render in cmd/guide.go.
Topics are supplied by the caller as a pre-ordered slice; the viewer preserves that order on the tab strip. No kanji labels — per Plan 28 Session 2026-04-23 decision D1 the guide surface is English-only.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Topic ¶
type Topic struct {
Key string // machine identifier, e.g. "quickstart"
Label string // full label, e.g. "QUICKSTART"
Short string // narrow-width fallback, e.g. "START"
Markdown string // raw embedded markdown body
}
Topic is one guide entry shown on a tab. Label is the full-width cell text; Short is the narrow-width fallback rendered when the full strip would exceed the clamp budget. Markdown is the raw embedded content (frontmatter retained — the renderer strips it at render time so the cache key stays pure).
func NewTopics ¶
NewTopics builds the []Topic slice in canonical order from a rawContents map keyed by machine identifier. Missing keys are silently skipped — the caller (cmd/guide.go) owns the "unknown topic" error path, so the viewer tolerates an incomplete map without panicking.
Order is locked to canonicalOrder regardless of the rawContents iteration order — map iteration is non-deterministic and the tab strip must render the same sequence every invocation.
type ViewerStage ¶
ViewerStage is the BubbleTea model for `bonsai guide`. Four topic tabs across the top, a scrollable glamour-rendered body below, standard initflow chrome wrapping the whole frame. Embeds initflow.Stage with railHidden=true — guide is read-only (no mutation process, no rail segments to walk).
Markdown output is cached per (topicIdx, viewport width) so resize events only re-render when the width actually changes and tab cycles re-use prior renders.
func NewViewer ¶
func NewViewer(topics []Topic, initialKey, version, projectDir string) *ViewerStage
NewViewer constructs a ViewerStage from the given topics + the initial topic key (empty or unknown → idx 0). version and projectDir feed the header chrome; projectDir may be empty if the caller couldn't resolve it (guide doesn't strictly need it).
func (*ViewerStage) Init ¶
func (s *ViewerStage) Init() tea.Cmd
Init implements tea.Model. No-op — the first render is wired up on the first WindowSizeMsg.
func (*ViewerStage) Result ¶
func (s *ViewerStage) Result() any
Result implements harness.Step. Guide is read-only — no payload.
func (*ViewerStage) Update ¶
Update implements tea.Model.
Keys:
- tab / right / l — next topic (wraps)
- shift+tab / left / h — prev topic (wraps)
- g / home — viewport top
- G / end — viewport bottom
- up/down, j/k, pgup/pgdn, space, u/d — scroll (viewport)
- q / esc / ctrl+c — quit
func (*ViewerStage) View ¶
func (s *ViewerStage) View() string
View composes the full frame: header + tab strip + viewport + footer. Stage.RenderFrame short-circuits to the min-size floor when the terminal falls below the 70×20 threshold, so the check isn't duplicated here.