app

package
v0.0.17 Latest Latest
Warning

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

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

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ArgSpec

type ArgSpec int

ArgSpec describes what a command does with the text after its name.

const (
	// ArgNone: the command takes no argument and rejects one rather than
	// silently ignoring it. Silently discarding input is how ":quit now"
	// becomes a bug report about a command that "sometimes does nothing".
	ArgNone ArgSpec = iota
	// ArgOptional: the argument changes behaviour but may be omitted.
	ArgOptional
	// ArgRequired: without it the command cannot run.
	ArgRequired
)

type AuthErrorMsg

type AuthErrorMsg struct {
	Err error
}

AuthErrorMsg is sent when the Telegram client fails during authentication.

type AuthStateChangedMsg

type AuthStateChangedMsg struct {
	State int
	Hint  string
}

AuthStateChangedMsg is sent from the authorizer callback.

type AuthenticatedMsg

type AuthenticatedMsg struct {
	UserId    int64
	FirstName string
	LastName  string
}

AuthenticatedMsg signals that authentication is complete.

type ClipboardPasteFailedMsg

type ClipboardPasteFailedMsg struct {
	Err error
}

ClipboardPasteFailedMsg reports why a clipboard paste produced nothing.

type ClipboardPastedMsg

type ClipboardPastedMsg struct {
	// ChatId is the chat that was active when the paste was requested. If
	// the active chat has changed by the time this message arrives, the
	// paste is discarded rather than installed into the wrong chat.
	ChatId  int64
	Path    string
	IsImage bool
}

ClipboardPastedMsg carries a clipboard image that has been spooled to disk and is ready to attach.

type Command

type Command struct {
	// Name is the command word, without the colon.
	Name string
	// Arg describes the argument, and Placeholder names it for display
	// (e.g. "<query>"). Placeholder must be empty when Arg is ArgNone.
	Arg         ArgSpec
	Placeholder string
	// Description is one line, shown in the palette.
	Description string
	// Key is the equivalent key binding shown right-aligned, so the palette
	// teaches the keymap rather than duplicating it. Empty when there is no
	// single key for the command.
	Key string
	// Run performs the command. It receives the model by value and returns
	// the updated model, matching how Update threads state everywhere else.
	// The returned string is a notice for the user; empty means silent.
	Run func(m Model, arg string) (Model, tea.Cmd, string)
}

Command is one entry in the registry.

A single typed table supplies everything about a command — its name, argument shape, description, key equivalent, and what it does — so the palette, the help card, and any future `:keymap` output all read from the same place and cannot drift apart. That is the whole reason this is a table rather than a switch in Update.

type ErrorMsg

type ErrorMsg struct {
	Err error
}

ErrorMsg carries an error to display.

type FocusChangedMsg

type FocusChangedMsg struct {
	Panel FocusPanel
}

FocusChangedMsg signals a focus panel change.

type FocusPanel

type FocusPanel int

FocusPanel identifies which UI panel has focus.

const (
	PanelChatList FocusPanel = iota
	PanelChatView
	PanelComposer
	PanelSearch
	PanelContacts
)

type InteractionMode

type InteractionMode int

InteractionMode is the app-level answer to one question: will the next printable key be typed as text, or acted on as a command?

TUI 2.0 makes that question answerable at a glance (docs/tui-2.0.md, "Mode integration", resolved by decision 3). The mode is **derived**, never stored: it is computed from focus, the composer's own editing state, and which overlay owns the keyboard. There is deliberately no mode field to set, because a second source of truth beside FocusPanel could contradict what the app actually does with a keystroke — and a badge that lies about that is worse than no badge.

const (
	// ModeNormal means printable keys act rather than type. It covers the
	// browsing panels and the overlays that navigate rather than collect
	// text.
	ModeNormal InteractionMode = iota
	// ModeInsert means printable keys are inserted as text: the composer
	// with an editor that will accept them, a text-collecting overlay, or
	// the auth form.
	ModeInsert
	// ModeVi means the composer is in vi editing and has returned to its
	// command state: the next letter runs a vi command on the draft
	// (decision I-12).
	//
	// It shared NORMAL with the browsing panels for a release, while
	// sharing none of their keys: q, r, y, e and ? are all inert there,
	// and i and h/l mean something else. A badge whose job is "what does
	// the next key do" cannot honestly say NORMAL for two keymaps that
	// agree on nothing.
	ModeVi
	// ModeCommand means the command palette owns input.
	ModeCommand
)

func (InteractionMode) String

func (m InteractionMode) String() string

type Model

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

func New

func New(cfg *config.Config, tg *telegram.Client, s *store.Store, authorizer *telegram.TUIAuthorizer) Model

func (Model) Init

func (m Model) Init() tea.Cmd

Init starts the chrome tick. Without it the top bar's clock would show the time of the last window resize for the rest of the session, and a transient notice would own the hint bar until something replaced it.

func (Model) Mode

func (m Model) Mode() InteractionMode

Mode reports the current interaction mode.

This is the single source the mode badge, the hint bar and the palette's `:` routing must consult, so that all three agree with what Update actually does with a keystroke. It is derived from the surface (see hints.go) rather than resolved a second time beside it: two derivations of one thing is how the bar and the badge came to disagree about which keymap was live.

It is NOT a drop-in replacement for the focus checks in Update, and retrofitting it onto them would change behaviour — see the `:` and backtick gates, which consult it deliberately and differently.

func (Model) Update

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd)

Update handles one message and then reconciles the frame with whatever it did.

The reconciliation is here rather than at the end of the switch because the switch has sixty-five early returns, and a step that only runs when the code happens to fall out of the bottom is a step that runs for most messages and silently not for the ones that matter.

func (Model) View

func (m Model) View() tea.View

type ScreenState

type ScreenState int

ScreenState identifies the current top-level screen.

const (
	ScreenAuth ScreenState = iota
	ScreenLoading
	ScreenMain
)

type SendFailedMsg

type SendFailedMsg struct {
	Err        error
	ChatId     int64 // chat the send was for; restore only into that composer
	Attachment string
	AsPhoto    bool
}

SendFailedMsg reports a send that failed after the composer was already reset. It carries the attachment back so it can be restored for a retry instead of being lost with the spool file.

type Surface added in v0.0.14

type Surface int

Surface is the thing whose keymap is live right now: a panel, or the overlay that has taken the keyboard from it.

It exists because the hint bar was keyed by MODE, and a mode is a much coarser question — "does the next printable key type or act?" — than "what can I press". Three surfaces share ModeNormal and agree on almost no keys between them, so the chat-view hint set showed in the chat list, under contacts, under a confirm dialog and in a vi composer: four places where it named keys that do nothing (decision I-6).

Every hint the app draws is keyed by this, including the ones components paint themselves — the chat list footer, the dialog's own line, the media overlay's row. A hint that names an inert key is a defect, not a nit: it is how "u unread" sat in the chat list footer for a release with nothing bound to u.

const (
	// The browsing panels.
	SurfaceChatList Surface = iota
	SurfaceChatView

	// The composer, in each of its two keymaps. They are separate surfaces
	// rather than one, because vi's command state shares nothing with
	// insert: enter still sends, but i, o, dd and : are the keys worth the
	// row, and ctrl+j inserts nothing there at all.
	SurfaceComposerInsert
	SurfaceComposerVi

	// The overlays, in the order Update consults them.
	SurfaceReactions
	SurfaceAttach
	SurfacePalette
	SurfaceMedia
	SurfaceHelp
	SurfaceDialog
	SurfaceSearch
	SurfaceContacts

	// The screens that are not the client: the auth form takes text, and
	// the loading screen takes nothing at all.
	SurfaceAuth
	SurfaceLoading
)

func (Surface) Mode added in v0.0.14

func (s Surface) Mode() InteractionMode

Mode is the badge's answer for this surface: will the next printable key be typed as text, acted on, or collected into a command?

The mode is derived FROM the surface rather than beside it. Two derivations of one thing is what let the hint bar and the badge disagree about which keymap was live; there is one now, and this is the projection of it onto the coarser question the badge asks.

func (Surface) String added in v0.0.14

func (s Surface) String() string

Jump to

Keyboard shortcuts

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