activity

package
v0.29.0 Latest Latest
Warning

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

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

Documentation

Overview

Package activity demonstrates per-row in-flight state: the spinner and status label a row shows while the server says something is working on it.

It talks to demoapi over HTTP — a real handler, a real client, real request boundaries — so the timings below are the server's, not a sleep in this file's goroutine.

The read-only half is three lines, and they are all in SetTheme

o.ActivityColumn = "Sync"
settled := activity.Settled(demoapi.SyncSynced, demoapi.SyncOutOfSync)
o.ActivityWhen = func(c table.Row) (string, bool) { return settled(c[colSync]) }

demoapi starts a scheduled sync every few seconds. Nobody pressed anything; those rows spin because the poll brought back "Syncing" and the predicate recognised it as work. That is the entire mechanism. A read-only dashboard — a poll, a keyed swap and a predicate — needs nothing else in this file.

So what is everything else doing here

The action menu, marking, the console streaming and the generation-stamped fetch are composition *around* the indicator rather than part of it. They are here because this is the screen shape the feature exists for, and because two of them are easy to get wrong in ways that break it:

  • The rows are keyed (SetKeyedRows), which activity requires for the same reason marking does — a refresh reorders the set constantly, and an indicator held by index would drift onto a neighbour.
  • Replies are stamped and stale ones dropped. Over a real network they do arrive out of order, and a stale page painted over a newer one is the most common way an indicator appears to flap.

Work the user starts, and the three lines that make it safe

Press "a" and pick Sync and the row moves at once, before the server has said anything — that is Expect, in the action.ChosenMsg case of Update. The claim covers the window between the keypress and the observation that reports it, and every observation retires claims, so it cannot outlive what the data can speak to.

The rest of that case is the part worth copying. A claim is only safe if no read taken across the write can reach the component:

s.gen++      // reads already in flight predate the keypress
s.writing++  // reads issued from here until the ack predate the write

with the matching `writing > 0` check where replies are applied. Delete the second line and a poll fired 50ms after the keypress comes back with the pre-click value — a newer generation carrying an older truth — and the row blinks off and on at a moment set by the poll phase rather than by anything the user did.

The two retractions are the other half. A write the server refuses takes its own claim back (politeness — the next poll would too); a *read* that fails takes them all back, which is not politeness, because an outage is exactly the case where no observation is coming to retire anything.

Options.Settle stays at its default of zero here, and that is correct for this server: demoapi's handler sets the status inline, so the first read after the ack already says Syncing. A reconciler — Argo, a Kubernetes watch cache — would need one or two, an allowance of observations that say nothing new rather than a duration nobody can know.

Refresh is still the pointed case: the server finishes it long before the next poll, so the claim covers the whole of it and the first observation retires it. The row spins for the round trip and then stops, which is the truth — as opposed to work somebody else began and ended between two polls, which nothing here can see.

What this screen does not contain

No spinner. No ticker. No re-pushing rows per frame to animate anything. The rows are pushed once per poll and the indicator is drawn over them by the component.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(t theme.Theme) screen.Screen

New returns the activity demo screen.

Types

type Screen

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

Screen is a table over demoapi, plus a poll. Everything about the indicators is declared in SetTheme and Actions; nothing here drives them.

func (*Screen) Actions

func (s *Screen) Actions() action.Set

Actions is this screen's whole contribution to the indicators it shows for work the user starts: Targets, and a Busy label per verb.

func (*Screen) Help

func (s *Screen) Help() []key.Binding

func (*Screen) HelpSections

func (s *Screen) HelpSections() []help.Section

func (*Screen) Init

func (s *Screen) Init() tea.Cmd

func (*Screen) IsCapturingKeys

func (s *Screen) IsCapturingKeys() bool

func (*Screen) Layout

func (s *Screen) Layout() layout.Node

func (*Screen) OnEnter

func (s *Screen) OnEnter(any) tea.Cmd

func (*Screen) SetTheme

func (s *Screen) SetTheme(t theme.Theme)

func (*Screen) Title

func (s *Screen) Title() string

func (*Screen) Update

func (s *Screen) Update(msg tea.Msg) (screen.Screen, tea.Cmd)

Update forwards everything to both the table and the poll. Nothing here touches the indicators: the table observes its own rows when SetKeyedRows hands them over, and animates them off the spinner ticks rule 6 delivers.

Jump to

Keyboard shortcuts

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