// Package contracts is a dependency-free teaching fixture, not a UI framework.
// Its state is owned by one event loop; it is deliberately not thread-safe.
package contracts
type phase uint8
const (
idle phase = iota
debouncing
loading
ready
failed
stopped
)
type searchState struct {
generation uint64
query string
phase phase
rows []string
err error
}
// change records new intent now, before the debounce timer fires. This fixture
// clears rows; an application may instead retain them with an explicit stale tag.
// The real application must also cancel its superseded backend context.
func (s *searchState) change(query string) uint64 {
s.generation++
s.query = query
s.phase = debouncing
s.rows = nil
s.err = nil
return s.generation
}
// begin admits one request for the current intent, rejecting stale or duplicate
// debounce messages. A same-query retry must first create new intent with change.
func (s *searchState) begin(generation uint64) bool {
if generation != s.generation || s.phase != debouncing {
return false
}
s.phase = loading
return true
}
// finish transfers an immutable result to this state only while its request is
// active. The producer must not mutate rows concurrently, even during this copy.
func (s *searchState) finish(generation uint64, rows []string, err error) bool {
if generation != s.generation || s.phase != loading {
return false
}
if err != nil {
s.phase = failed
s.rows = nil
s.err = err
return true
}
s.phase = ready
s.rows = append([]string(nil), rows...)
s.err = nil
return true
}
// stop invalidates pending completions. It does not stop real I/O; the caller
// must cancel and clean up the application's owned resources as well.
func (s *searchState) stop() {
s.generation++
s.phase = stopped
}
// bodyRows handles nonnegative row allocation. compact is true when the caller
// must use a different layout because the chrome does not fit. It does not
// measure text or model Lip Gloss's box behavior.
func bodyRows(total, header, footer int) (rows int, compact bool) {
total = max(0, total)
header = max(0, header)
footer = max(0, footer)
if header > total || footer > total-header {
return 0, true
}
return total - header - footer, false
}