pane

package
v0.0.0-...-1586973 Latest Latest
Warning

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

Go to latest
Published: Aug 27, 2026 License: MIT Imports: 32 Imported by: 0

Documentation

Overview

Package pane holds what the full-screen detail views — the comment section and reader mode — share: the content-aware viewport and search engine, frame chrome and footers, browser commands, and the standalone runner.

Index

Constants

View Source
const (
	StatusMessageShort = 2 * time.Second
	StatusMessageLong  = 3 * time.Second
)
View Source
const ReaderFetchTimeout = 15 * time.Second

ReaderFetchTimeout bounds fetching and parsing a page for reader mode — a story's article or a followed link — in both shells. One constant, so the two cannot drift apart.

Variables

View Source
var CancelKeys = key.NewBinding(key.WithKeys("esc", "backspace", "ctrl+c"))

CancelKeys stops an in-flight fetch; the app's keymap shares it so both shells cancel on the same keys.

View Source
var ProgressOut io.Writer = os.Stderr
View Source
var TitleOut io.Writer = os.Stdout

Functions

func CancelledStatus

func CancelledStatus() string

CancelledStatus is the message shown when an in-flight operation is cancelled by hand.

func ClearProgress

func ClearProgress()

func CommittedSearchLabel

func CommittedSearchLabel(query, icon string, enableNerdFonts bool) string

CommittedSearchLabel renders a committed query dimmed behind its sigil. Nerd fonts trade the / for the done-searching glyph — icon overrides it, empty picks the shared default; views that keep the plain sigil even under nerd fonts (the front page) pass enableNerdFonts=false.

func DepthBadge

func DepthBadge(trail []message.TrailEntry) string

DepthBadge marks a page reached by following links in place: one chevron per page behind it, each one a quit-key step back, colored by what that step lands on — yellow for a comment section, matching thread links in running text, green for an article. The faint fallback keeps a linked page with no chain at all from rendering an empty badge.

func DetectGraphics

func DetectGraphics() tea.Cmd

DetectGraphics probes the terminal for the Kitty graphics protocol and its cell pixel size, enabling high-resolution reader images. Terminals that answer confirm through HandleGraphicsReport; no answer leaves the half-block art. Terminals the probe would misfire on return nil.

func DetectStyledUnderline

func DetectStyledUnderline() tea.Cmd

DetectStyledUnderline resolves whether the terminal can draw the dashed underline inert reader links use. iTerm2, Alacritty and VTE-based terminals (0.52+) draw styled underlines but do not answer XTGETTCAP, so their environment markers enable it directly. Apple's Terminal.app prints DCS queries to the screen instead of consuming them, so it is never queried. Every other terminal is asked for the Smulx terminfo capability and affirms it with a CapabilityMsg; no answer leaves the plain underline.

func EmitKittyWork

func EmitKittyWork(work []article.KittyWork)

EmitKittyWork queues a render's image work — PNGs not yet transmitted, placements whose geometry changed — for ordered delivery to the terminal. Call from the update loop only: submission order must match the render order the delta protocol's bookkeeping saw.

func ExtractPromptCursor

func ExtractPromptCursor(frame string) (x, y int, cleaned string, ok bool)

ExtractPromptCursor locates the prompt cursor in a rendered frame, blanks its cell and reports the position, for the program to park the real terminal cursor there — the cursor the terminal draws in its own configured color and blink. Every open search prompt lives in a footer or status row, so only the frame's last line is scanned; ok is false when no prompt is open. The typed query itself may contain the marker rune — the cursor is appended after the input, so the last occurrence is the cursor.

func FetchPage

func FetchPage(ctx context.Context, fetchID uint64, url string, trail []message.TrailEntry) tea.Cmd

FetchPage loads a page reached by following a link inside an article, for whichever shell dispatched it. Unlike a story fetch there is no story: no title to validate against, and nothing marked read. trail rides along untouched — it becomes the new page's walk-back chain.

func FetchThread

func FetchThread(ctx context.Context, fetchID uint64, fetch ThreadFetcher, id int, trail []message.TrailEntry) tea.Cmd

FetchThread loads a Hacker News discussion linked inside an article, for the standalone shells. Nothing is marked read — the one-shot commands leave no history — so the thread arrives as if fully visited, exactly as the comments subcommand serves its own.

func FirstLinkOnScreen

func FirstLinkOnScreen(links []Link, top, height int) int

FirstLinkOnScreen is the topmost link in the viewport window, or -1 with none in view.

func FooterSeparator

func FooterSeparator(width int) string

func FooterSeparatorWithLabel

func FooterSeparatorWithLabel(width, rightEdge int, label string, termFG, termBG color.Color) string

FooterSeparatorWithLabel is FooterSeparator with label written into the rule, ending at rightEdge. The label's glyphs render dim while the underline through them keeps the rule's full strength: the underline color is pinned to the rule's own (SGR 58) and the text dims through an explicitly blended foreground — SGR faint cannot deliver this, terminals dim the underline decoration along with the glyphs no matter the underline color. termFG/termBG are the terminal's reported colors; without them the blend has no inputs and the label falls back to faint, dimming its stretch of the rule with it.

func FriendlyError

func FriendlyError(err error) string

func HandleGraphicsReport

func HandleGraphicsReport(msg tea.Msg) bool

HandleGraphicsReport records a terminal's answer to the graphics probes and reports whether the state changed — the caller then repaints an open reader so its images pick up the new mode or geometry.

func LinkOnScreen

func LinkOnScreen(l Link, top, height int) bool

LinkOnScreen reports whether any of the link's spans lies in the viewport window starting at top, height lines tall.

func LinkSelectorLabel

func LinkSelectorLabel(viewable, nerdFonts bool) string

LinkSelectorLabel is the footer label while a URL selector is up: the selector icon and a faint mode label, while the URL itself rides the separator above. A link the view won't open breaks the arrow, matching its dimmed URL; an empty selection keeps the plain arrow — nothing is inert, there is just nothing selected yet.

func LinkURLRow

func LinkURLRow(paneWidth, leftMargin, contentWidth int, url string, termFG, termBG color.Color) string

LinkURLRow is the footer separator while a URL selector is up: the selected link's URL written into the rule, right-aligned against the content column's edge, leaving the footer line to the mode label. The URL renders faint under the rule's full-strength underline; whether the link opens shows in the footer arrow and the selection bar, not here.

func LinkViewable

func LinkViewable(rawURL string) bool

LinkViewable reports whether following the link can open a view in place: a page reader mode could render, or a Hacker News discussion the comment section opens. Links that can't (PDFs, media, archives, blocked domains) are selectable but inert, marked by the red selection bar and a dimmed footer URL.

func LoadingTitleHeader

func LoadingTitleHeader(title string, enableNerdFonts bool, leftMargin, screenWidth int) string

LoadingTitleHeader is TitleHeader without the bold: the detail pane shows it while the story loads and on the error view when the load fails, so the title gains its full weight only once the content is in.

func MatchCountLabel

func MatchCountLabel(current, total int) string

MatchCountLabel is the "3/17" match counter, or "no matches".

func MatchTotalLabel

func MatchTotalLabel(total int) string

MatchTotalLabel is the counter while the prompt is open: hits have no current position yet, so only the running total shows.

func MoveLink(links []Link, current, direction, top int) int

MoveLink selects the next link wherever it sits, wrapping like a search jump. From an empty selection (-1) it leaves relative to the viewport: forward to the first link past its top, backward to the last one above it.

func NewSpinner

func NewSpinner() spinner.Model

func NewViewport

func NewViewport(width, height int) viewport.Model

NewViewport returns a viewport with the bindings the detail views handle themselves (paging) disabled, along with mouse wheel handling. Horizontal scrolling is disabled entirely: pane content is pre-wrapped to fit, and DecorateView's row mapping relies on a zero x-offset.

func OpenCommentsInBrowser

func OpenCommentsInBrowser(id int) tea.Cmd

func OpenStoryInBrowser

func OpenStoryInBrowser(url string, id int) tea.Cmd

OpenStoryInBrowser opens the story URL, falling back to the HN item page for text posts. Returns nil when there is nothing to open (standalone reader on an arbitrary URL-less item).

func OverlayStatus

func OverlayStatus(view, status string, width int) string

OverlayStatus writes fetch and status feedback onto the last row of a detail view, which reserves that row as footer space. width is the pane the view fills.

func PromptLabel

func PromptLabel(input string, enableNerdFonts bool) string

PromptLabel renders an open prompt: the sigil zone, the typed text and the cursor.

func PromptSigilWidth

func PromptSigilWidth(enableNerdFonts bool) int

PromptSigilWidth is the cells the prompt zone occupies before the typed text. Views that keep the text column identical between the open prompt and the committed query outdent the prompt by the difference.

func QueryCellSize

func QueryCellSize() tea.Cmd

QueryCellSize re-asks for the cell pixel size on a resize, so a font-size change re-derives image geometry. Nothing to keep honest before the graphics probe succeeded.

func RepaintAfterGrow

func RepaintAfterGrow() tea.Cmd

RepaintAfterGrow works around a renderer bug that surfaces when the terminal grows wider: ultraviolet resizes its model of the screen but backfills only newly added rows, never the columns a grow adds to existing rows (TerminalRenderer.Render's sync loop), so it records those columns as blank while the resize redraw put content there. Anything in them that later becomes blank diffs as unchanged and lingers on screen as ghost text. A full repaint after that redraw has flushed — once the model has its correct dimensions — resyncs the model with the screen.

func RunStandalone

func RunStandalone(title string, makeView func(width, height int) View, opts StandaloneOptions) error

RunStandalone runs a detail view as its own program under the window title title; makeView receives the terminal dimensions from the first WindowSizeMsg. The options' factories let links followed inside the view open in place; absent ones fall back to the browser.

func SaveWindowTitle

func SaveWindowTitle() (restore func())

SaveWindowTitle pushes the terminal's current window title and returns the pop that restores it. Call it before the program starts and the restore after it exits: like the progress sequences, a direct write is only safe where no frame flush can interleave with it.

func SetProgressIndeterminate

func SetProgressIndeterminate()

func SetProgressPercent

func SetProgressPercent(pct int)

func SyncProgress

func SyncProgress(err error)

SyncProgress settles the indicator for a finished fetch: an error stays visible for the status message lifetime, success clears it. Call only from the Update loop after the finish guard, so a stale fetch can never write over its successor's indicator.

func TitleHeader

func TitleHeader(title string, enableNerdFonts bool, leftMargin, screenWidth int) string

TitleHeader renders the bold, highlighted story title over an underline separator spanning the screen.

func TitleHeaderWithBadge

func TitleHeaderWithBadge(title, badge string, enableNerdFonts bool, leftMargin, rightEdge, screenWidth int) string

TitleHeaderWithBadge is TitleHeader with a pre-styled badge ending at rightEdge — the content column's right edge, not the screen's. The title truncates early enough that the two never collide. A pane too narrow to keep any title next to the badge drops the badge instead.

func UpdateSpinner

func UpdateSpinner(sp spinner.Model, msg spinner.TickMsg, active bool) (spinner.Model, tea.Cmd)

UpdateSpinner advances the animation and reschedules the next tick only while active, so a stopped spinner's tick chain dies out.

func WindowTitle

func WindowTitle(text string) string

WindowTitle prepares text for use as a window title. OSC 2 ends at the first BEL or ST, so a control character in a story title would close the sequence early and let whatever follows land on the terminal as commands. Titles are already stripped at ingest — this is the sink saying so itself, as ansi.Hyperlink does for its target.

func WireGraphics

func WireGraphics(p *tea.Program) (stop func())

WireGraphics routes image work into p's message loop. Call it before Run; the returned stop ends the worker once the program has exited.

func WireProgress

func WireProgress(p *tea.Program) (settle func())

WireProgress routes progress sequences into p's message loop. Call it before Run: the pre-start write to progressCh is the happens-before edge that lets fetch goroutines read it without locks. The forwarder goroutine exists because Send would deadlock called from Update itself; once the program stops, Send is a no-op. The returned settle stops the forwarder and clears the indicator — call it after Run returns, when a direct write can no longer interleave with a frame; clearing there rather than in the quit paths guarantees the indicator never outlives the program, whatever the exit.

Types

type CommonKeyMap

type CommonKeyMap struct {
	Quit         key.Binding
	Help         key.Binding
	GotoTop      key.Binding
	GotoBottom   key.Binding
	HalfPageDown key.Binding
	HalfPageUp   key.Binding
	PageDown     key.Binding
	PageUp       key.Binding
	OpenLink     key.Binding
	OpenComments key.Binding
	NextStory    key.Binding
	PrevStory    key.Binding
	ToggleWide   key.Binding

	// Search bindings apply while a search is active; n/N then take
	// precedence over each view's own jump bindings.
	Search      key.Binding
	NextMatch   key.Binding
	PrevMatch   key.Binding
	ClearSearch key.Binding
}

CommonKeyMap holds the bindings shared by both detail views; each view embeds it and adds its own.

func DefaultCommonKeyMap

func DefaultCommonKeyMap() CommonKeyMap

func (*CommonKeyMap) DisableAppKeys

func (k *CommonKeyMap) DisableAppKeys()

DisableAppKeys removes the bindings that need the surrounding app — the J/K adjacent-story jumps and the z layout toggle — for standalone use where there is no story list and no split layout.

type FetchGuard

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

FetchGuard owns the identity and cancellation of the single fetch a shell allows in flight. Begin invalidates any predecessor and hands out the new fetch's token; Finish drops stale results; Abort cancels and moves to a new era, so a result the fetch managed to deliver before the cancel took hold can never match.

func (*FetchGuard) Abort

func (g *FetchGuard) Abort() bool

Abort cancels the in-flight fetch; false means nothing was in flight.

func (*FetchGuard) Begin

func (g *FetchGuard) Begin(timeout time.Duration) FetchToken

Begin starts a fetch's lifecycle, invalidating any predecessor. A zero timeout means the fetch runs until finished or aborted.

func (*FetchGuard) CurrentID

func (g *FetchGuard) CurrentID() uint64

CurrentID stamps era-scoped side effects made outside Begin (e.g. an error-display timeout) so they can be ignored once a newer fetch begins.

func (*FetchGuard) Finish

func (g *FetchGuard) Finish(id uint64) bool

Finish ends the in-flight fetch if id belongs to it; false is a stale result the caller must drop.

func (*FetchGuard) InFlight

func (g *FetchGuard) InFlight() bool

type FetchToken

type FetchToken struct {
	Ctx context.Context //nolint:containedctx // fetch-scoped, never stored beyond the Update cycle
	ID  uint64
}

FetchToken is a fetch's identity: the context its commands must fetch under and the id their results must carry. Only Begin hands one out, so a fetch command can only be built for the fetch actually starting. A token held across Update cycles goes stale — results built from it fail the Finish guard and are dropped.

type Link struct {
	URL      string
	Spans    []Match
	Viewable bool
	Thread   bool
}

Link is one followable URL in a rendered document: the OSC 8 target and the cell spans its anchor text occupies — one span per rendered line for links the wrap split. Thread marks a Hacker News discussion link, whose selection paints in thread yellow rather than link blue.

func ExtractLinks(lines []string, fromLine int) []Link

ExtractLinks locates every OSC 8 hyperlink in the rendered lines from fromLine on. The wrapper closes and reopens a hyperlink at each line break, so spans of the same URL on adjacent lines are one link, while the same URL opening twice on one line — or in separate paragraphs, a blank line apart — is two.

type MakePageView

type MakePageView func(entry message.TrailEntry, trail []message.TrailEntry, width, height int) View

MakePageView rebuilds a reader page around a followed link — opening one, or walking back through the trail. entry carries the page's parse, trail the chain behind it.

type MakeThreadView

type MakeThreadView func(thread *comment.Thread, lastVisited int64, trail []message.TrailEntry, width, height int) View

MakeThreadView rebuilds a comment section around a followed Hacker News discussion link — opening one, or walking back through the trail.

type Match

type Match struct {
	Line      int
	StartCell int
	EndCell   int
}

Match locates one search hit: a cell span on a content line.

func FindMatches

func FindMatches(plainLines []string, query string) []Match

FindMatches locates query in the given plain lines as cell spans. Smart case: an all-lowercase query matches case-insensitively, any uppercase character makes the match exact.

type PromptResult

type PromptResult int

PromptResult reports how the search prompt reacted to a key press.

const (
	PromptPending PromptResult = iota
	PromptCommitted
	PromptCanceled
)

type RowOverride

type RowOverride struct {
	Line    int
	Content string
}

RowOverride substitutes the rendered row at content line Line for display only, leaving the stored content untouched. The comment section uses it to swap in the focused header variant without rebuilding the document.

type Scroller

type Scroller struct {
	Viewport     viewport.Model
	ContentLines int // excludes bottom padding

	// SearchCommittedIcon replaces the shared done-searching glyph in the
	// footer's search label; empty keeps the default.
	SearchCommittedIcon string
	// contains filtered or unexported fields
}

Scroller wraps a viewport with content-aware scrolling: paging and clamping are computed against the real content length, ignoring the bottom padding SetLines appends so any line can scroll to the top.

func (*Scroller) ActiveQuery

func (s *Scroller) ActiveQuery() string

ActiveQuery is the text matches should be computed against: the live prompt input while typing, the committed query otherwise. Views recompute matches from it on every prompt key, so hits highlight as the user types, and a canceled prompt falls back to the prior query's matches.

func (*Scroller) ClampScroll

func (s *Scroller) ClampScroll(before int)

ClampScroll prevents scrolling down past the last content line while still allowing upward scrolling from a position beyond the clamp point (e.g. after an n/N jump).

func (*Scroller) ClearSearch

func (s *Scroller) ClearSearch()

ClearSearch drops the query and matches; highlights disappear on the next frame since DecorateView applies them per render.

func (*Scroller) DecorateView

func (s *Scroller) DecorateView(view string) string

DecorateView applies the display-time decorations to the viewport's rendered window: row overrides first, then search-match overlays on top, so a match inside a focused header highlights the focused variant. Row k of the view shows content line YOffset+k — the invariant that makes this mapping valid is that pane content never scrolls horizontally.

func (*Scroller) Forward

func (s *Scroller) Forward(msg tea.Msg) tea.Cmd

Forward passes msg to the viewport and clamps the resulting scroll.

func (*Scroller) GotoBottom

func (s *Scroller) GotoBottom()

GotoBottom scrolls the last line of real content to the bottom of the viewport, ignoring the bottom padding.

func (*Scroller) HalfPageDown

func (s *Scroller) HalfPageDown()

func (*Scroller) HalfPageUp

func (s *Scroller) HalfPageUp()

func (*Scroller) HandleMouseWheel

func (s *Scroller) HandleMouseWheel(msg tea.MouseWheelMsg)

func (*Scroller) HandleSearchPromptKey

func (s *Scroller) HandleSearchPromptKey(msg tea.KeyPressMsg) PromptResult

HandleSearchPromptKey feeds one key press to the open search prompt; a commit installs the typed text as the query, leaving any previous search untouched on cancel.

func (*Scroller) JumpToFirstMatchFrom

func (s *Scroller) JumpToFirstMatchFrom(line int)

JumpToFirstMatchFrom jumps to the first match at or below the given line, wrapping to the first match overall when none follows.

func (*Scroller) JumpToMatch

func (s *Scroller) JumpToMatch(i int)

JumpToMatch scrolls match i (wrapped into range) to sit a couple of lines below the viewport top and makes it the current match.

func (*Scroller) Lines

func (s *Scroller) Lines() []string

Lines is the stored content as styled lines, for views that scan the rendered output itself (the reader's link extraction).

func (*Scroller) LinkFetching

func (s *Scroller) LinkFetching() bool

func (*Scroller) LinkSpans

func (s *Scroller) LinkSpans() []Match

func (*Scroller) LinkSpansInert

func (s *Scroller) LinkSpansInert() bool

func (*Scroller) LinkSpansThread

func (s *Scroller) LinkSpansThread() bool

func (*Scroller) NextMatch

func (s *Scroller) NextMatch()

func (*Scroller) PageDown

func (s *Scroller) PageDown()

func (*Scroller) PageUp

func (s *Scroller) PageUp()

func (*Scroller) PlainLines

func (s *Scroller) PlainLines() []string

PlainLines is the content with ANSI styling stripped — the text as the user sees it, for matching against. Stripped on first use after a content change, so views that never need it don't pay for it. xansi.Strip (a parser walk) is used over ansi.Strip (a backtracking regexp): measured ~6× faster on escape-heavy content like image art.

func (*Scroller) PrevMatch

func (s *Scroller) PrevMatch()

func (*Scroller) RefreshPadding

func (s *Scroller) RefreshPadding()

RefreshPadding re-pushes the stored content so the bottom padding matches the current viewport height, for height-only resizes where the content itself is unchanged.

func (*Scroller) SearchActive

func (s *Scroller) SearchActive() bool

SearchActive reports whether a committed query is in effect.

func (*Scroller) SearchCountLabel

func (s *Scroller) SearchCountLabel() string

SearchCountLabel is the counter over the Scroller's own match list; views that navigate a larger match set format theirs with MatchCountLabel and MatchTotalLabel.

func (*Scroller) SearchFooterLabel

func (s *Scroller) SearchFooterLabel(enableNerdFonts bool) string

SearchFooterLabel is the footer text for the search state: the live prompt with a block cursor while typing, the committed query dimmed otherwise, empty when no search is in play. Nerd fonts swap the faint / for a magnifier at full strength, like the footer's other icons, with extra room after the wide glyph; committing trades the magnifier for a done-searching glyph, SearchCommittedIcon or the shared default.

func (*Scroller) SearchMatches

func (s *Scroller) SearchMatches() []Match

func (*Scroller) SearchPrompting

func (s *Scroller) SearchPrompting() bool

func (*Scroller) SearchQuery

func (s *Scroller) SearchQuery() string

func (*Scroller) SetCurrentMatch

func (s *Scroller) SetCurrentMatch(i int)

SetCurrentMatch marks matches[i] as the current match for highlighting, for views that run match navigation outside the Scroller; -1 means the current match isn't in the installed list (hidden in a collapsed branch). Views that navigate through JumpToMatch/NextMatch/PrevMatch never call this — the jumps track the current match themselves.

func (*Scroller) SetLines

func (s *Scroller) SetLines(lines []string)

SetLines replaces the viewport content. A viewport-height of padding is appended so jump targets near the end can scroll to the top of the view; ClampScroll keeps ordinary scrolling within the real content.

func (*Scroller) SetLinkFetching

func (s *Scroller) SetLinkFetching(active bool)

SetLinkFetching repaints the selection in the muted in-flight colors while the followed link's fetch runs. The shell that owns the fetch toggles it: on at fetch start, off when the fetch ends without a page (failure, cancel) — success replaces the view, taking the state with it.

func (*Scroller) SetLinkSpans

func (s *Scroller) SetLinkSpans(matches []Match, inert, thread bool)

SetLinkSpans installs the spans of the link the reader's URL selector sits on; nil clears them. They paint over any search highlights — the selection is the thing being acted on. inert swaps the selection colors for the red bar, marking a link the view will not open; thread swaps them for the yellow bar of a Hacker News discussion link.

func (*Scroller) SetRowOverrides

func (s *Scroller) SetRowOverrides(overrides []RowOverride)

SetRowOverrides installs the display-time row substitutions. Overrides must not change a row's visible width.

func (*Scroller) SetSearchMatches

func (s *Scroller) SetSearchMatches(matches []Match)

SetSearchMatches installs the match list for the committed query. The caller computes the matches — how content maps to matchable text differs per view. Highlighting happens in DecorateView.

func (*Scroller) StartSearchPrompt

func (s *Scroller) StartSearchPrompt()

type StandaloneOptions

type StandaloneOptions struct {
	MakePageView   MakePageView
	MakeThreadView MakeThreadView
	FetchThread    ThreadFetcher
}

StandaloneOptions carries the factories a standalone shell follows links with. A nil factory sends its kind of link to the browser instead, so each subcommand grants exactly the views it can build.

type TextPrompt

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

TextPrompt is a one-line text input: the Scroller's search prompt and the front page's Hacker News search share its key handling, so the two prompts can never drift apart in feel.

func (*TextPrompt) Active

func (p *TextPrompt) Active() bool

func (*TextPrompt) HandleKey

func (p *TextPrompt) HandleKey(msg tea.KeyPressMsg) PromptResult

HandleKey feeds one key press to the open prompt. Printable characters append, enter commits the typed text, esc and backspacing past empty cancel. Committing an empty prompt also cancels.

func (*TextPrompt) Start

func (p *TextPrompt) Start()

func (*TextPrompt) Text

func (p *TextPrompt) Text() string

Text is the typed input; after PromptCommitted it holds the committed text.

type ThreadFetcher

type ThreadFetcher func(ctx context.Context, id int, onProgress func(fetched, total int)) (*comment.Thread, error)

ThreadFetcher loads a Hacker News discussion as the comment views consume it. The cmd layer supplies one wrapping its service, so this package stays ignorant of which backend answers.

type TransientStatus

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

TransientStatus is a footer status message that expires after a lifetime; the generation guards a stale timer from clearing a newer message.

func (*TransientStatus) Clear

func (t *TransientStatus) Clear()

Clear hides the message immediately and invalidates pending expiries.

func (*TransientStatus) Expire

func (t *TransientStatus) Expire(gen int) bool

Expire clears the message if gen is current — i.e. the timer that fired belongs to the visible message — and reports whether it did.

func (*TransientStatus) Message

func (t *TransientStatus) Message() string

func (*TransientStatus) Set

func (t *TransientStatus) Set(msg string, d time.Duration) tea.Cmd

Set shows msg and returns the command that expires it after d.

func (*TransientStatus) SetPermanent

func (t *TransientStatus) SetPermanent(msg string)

SetPermanent shows msg with no expiry: it stays until the next Set, SetPermanent or Clear. The generation bump invalidates pending expiry timers — the message may be modal text like the favorites prompt, which an earlier message's timer must not blank.

type View

type View interface {
	Init() tea.Cmd
	Update(tea.Msg) tea.Cmd
	View() string
}

View is the surface the standalone adapter drives; both detail views satisfy it.

Jump to

Keyboard shortcuts

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