feedback

package
v1.9.29 Latest Latest
Warning

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

Go to latest
Published: May 21, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Index

Constants

View Source
const DiscussionNodeID = "D_kwDOQh82-s4Alt9V"

DiscussionNodeID is the GitHub Discussion node ID for the Feedback Hub. Must be replaced with the real node ID before release. Retrieve after creating the Discussion at:

https://github.com/asheshgoplani/agent-deck/discussions

via: gh api graphql -f query='{ repository(owner:"asheshgoplani",name:"agent-deck") { discussions(first:5) { nodes { id title } } } }'

View Source
const DiscussionURL = "https://github.com/asheshgoplani/agent-deck/discussions"

DiscussionURL is the GitHub Discussions page for agent-deck feedback. Note: GitHub Discussions does NOT support ?body= URL parameter prefill (only GitHub Issues supports this). The browser fallback opens this URL and relies on the user pasting from clipboard into the Discussion form.

Variables

This section is empty.

Functions

func FormatComment

func FormatComment(version string, rating int, goos, goarch, comment string) string

FormatComment formats a feedback submission for posting to GitHub Discussions. Format: "**vVER** | **N/5** EMOJI | GOOS GOARCH\nCOMMENT" When comment is empty, the trailing newline and comment are omitted.

func MigrateLegacyOptOut added in v1.9.10

func MigrateLegacyOptOut(s *State, currentVersion string) bool

MigrateLegacyOptOut rewrites a pre-#967 forever-opt-out (FeedbackEnabled false with no OptOutVersion) into a per-series opt-out anchored at currentVersion. Idempotent: a state that already records an OptOutVersion or has feedback enabled is left untouched. Returns true if the caller should persist the result via SaveState.

Production wiring lives in cmd/agent-deck/main.go, which calls this from the TUI launch path right next to RecordLaunch — so the legacy migration happens exactly once per affected user, on their first TUI launch after upgrading to a binary that ships #967.

func MinDaysBeforeFirstPrompt added in v1.7.41

func MinDaysBeforeFirstPrompt() int

MinDaysBeforeFirstPrompt returns the configured floor (default 3).

func MinLaunchesBeforeFirstPrompt added in v1.7.41

func MinLaunchesBeforeFirstPrompt() int

MinLaunchesBeforeFirstPrompt returns the configured floor (default 7).

func PromptCooldownDays added in v1.7.41

func PromptCooldownDays() int

PromptCooldownDays returns the configured cooldown (default 14).

func RatingEmoji

func RatingEmoji(rating int) string

RatingEmoji maps a numeric rating (1-5) to an emoji. Returns "" for out-of-range values.

func RecordLaunch added in v1.7.41

func RecordLaunch(s *State, now time.Time)

RecordLaunch increments LaunchCount by 1 and seeds FirstSeenAt with now on the very first call. Subsequent calls never overwrite FirstSeenAt so pacing persists across version upgrades and state reloads. Does NOT save — caller must call SaveState.

func RecordOptOut

func RecordOptOut(s *State, currentVersion string)

RecordOptOut records a feedback dismissal scoped to the running release series. After #967 this is no longer a permanent kill-switch — the opt-out only suppresses prompts while the running version stays on the same MAJOR.MINOR line (e.g. "1.9.x"). The legacy FeedbackEnabled flag is still toggled off so external readers (config.toml sync, the explicit `agent-deck feedback` re-enable prompt) continue to see the user as opted out until something explicitly clears it. Does NOT save — caller must call SaveState.

func RecordRating

func RecordRating(s *State, currentVersion string, rating int)

RecordRating sets last_rated_version to currentVersion and resets shown_count to 0. Deliberately does NOT touch FirstSeenAt, LastPromptedAt, or LaunchCount — pacing signals survive a rating so the next version still paces against the user's real history. Does NOT save — caller must call SaveState.

func RecordShown

func RecordShown(s *State, now time.Time)

RecordShown increments shown_count by 1 and stamps LastPromptedAt with now so the cooldown engages for subsequent calls. Does NOT save — caller must call SaveState.

func SaveState

func SaveState(s *State) error

SaveState atomically writes the state to ~/.agent-deck/feedback-state.json. Uses tmp+rename to prevent partial writes (T-01-01).

func ShouldShow

func ShouldShow(s *State, currentVersion string, now time.Time) bool

ShouldShow returns true only when every gate is clear:

  1. the user is not opted out for the current release series (#967 — opt-out is scoped to MAJOR.MINOR, not forever)
  2. last_rated_version does not match currentVersion
  3. shown_count < max_shows
  4. user has been around for at least MinDaysBeforeFirstPrompt days AND has launched agent-deck at least MinLaunchesBeforeFirstPrompt times
  5. no prompt was shown within the last PromptCooldownDays days

Pure: never mutates state. Callers that want to track first-seen should call RecordLaunch at process start.

Types

type Sender

type Sender struct {
	// GhCmd runs the gh CLI with the given arguments.
	// Real implementation: exec.Command("gh", args...).CombinedOutput().
	GhCmd func(args ...string) error

	// BrowserCmd opens the given URL in the default browser.
	// Real implementation: runtime.GOOS switch (open/xdg-open).
	BrowserCmd func(url string) error

	// ClipboardCmd copies the given text to the system clipboard.
	// Receives the formatted COMMENT BODY (not a URL).
	// Real implementation: clipboard.Copy(text, false).
	ClipboardCmd func(text string) error

	// IsHeadlessFunc returns true when no graphical display is available.
	// Real implementation: platform.IsHeadless().
	IsHeadlessFunc func() bool
}

Sender holds the three-tier send mechanism for feedback submissions. All four function fields are injectable for testing.

func NewSender

func NewSender() *Sender

NewSender returns a *Sender with all four fields populated with real implementations.

func (*Sender) Send

func (s *Sender) Send(version string, rating int, goos, goarch, comment string) error

Send submits feedback using a three-tier fallback chain:

  1. gh CLI via GraphQL mutation (primary)
  2. If gh fails and not headless: copy body to clipboard, then open Discussion URL in browser
  3. If gh fails and headless: copy body to clipboard only (no browser)

version, rating, goos, goarch, comment are used to build the formatted comment body via FormatComment. Send returns nil in all fallback cases — the caller should inform the user that clipboard/browser fallback was used.

type State

type State struct {
	LastRatedVersion string    `json:"last_rated_version"`
	FeedbackEnabled  bool      `json:"feedback_enabled"`
	OptOutVersion    string    `json:"opt_out_version,omitempty"`
	ShownCount       int       `json:"shown_count"`
	MaxShows         int       `json:"max_shows"`
	LaunchCount      int       `json:"launch_count,omitempty"`
	FirstSeenAt      time.Time `json:"first_seen_at,omitempty"`
	LastPromptedAt   time.Time `json:"last_prompted_at,omitempty"`
}

State holds the persisted feedback preferences for a user. File: ~/.agent-deck/feedback-state.json. Always serializes all fields (D-05).

v1.7.41 added LaunchCount, FirstSeenAt, LastPromptedAt to pace the first prompt for new users. Serialized via RFC3339 through time.Time's MarshalJSON.

#967 added OptOutVersion to scope the dismiss-feedback opt-out to a single release series (MAJOR.MINOR). A user who declined at v1.9.5 is silenced only while the running version is still on the 1.9.x line; the next release-series bump re-shows the prompt. Pre-#967 state files stored a permanent FeedbackEnabled=false with no OptOutVersion — those are migrated in-memory in ShouldShow by treating them as "dismissed at the current series" (suppresses the immediate launch but expires on the next series bump).

func LoadState

func LoadState() (*State, error)

LoadState reads ~/.agent-deck/feedback-state.json and returns the state. If the file does not exist, it returns a default State (FeedbackEnabled=true, MaxShows=3). A missing file is NOT an error. A malformed file returns a default state to prevent crashes.

Jump to

Keyboard shortcuts

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