From 5c46cdba29bc10a1b3dc34388a711077712726d0 Mon Sep 17 00:00:00 2001 From: Tomas Vesely <448809+wham@users.noreply.github.com> Date: Wed, 31 Dec 2025 14:09:54 -0800 Subject: [PATCH 1/5] Enhance login UI with updated title and improved user prompts --- main.go | 64 +++++++++++++++++++++++++++++---------------------------- main.md | 33 ++++++++++++++++++++++------- 2 files changed, 59 insertions(+), 38 deletions(-) diff --git a/main.go b/main.go index 7873cdf..cf9100f 100644 --- a/main.go +++ b/main.go @@ -5554,19 +5554,21 @@ type AccessTokenResponse struct { // loginModel is the Bubble Tea model for the login UI type loginModel struct { - spinner spinner.Model - textInput textinput.Model - userCode string - verificationURI string - status string // "waiting", "org_input", "success", "error" - errorMsg string - username string - token string - organization string - homeDir string - width int - height int - done bool + spinner spinner.Model + textInput textinput.Model + userCode string + verificationURI string + status string // "waiting", "org_input", "success", "error" + errorMsg string + username string + token string + organization string + homeDir string + width int + height int + done bool + currentUsername string // current logged-in username for title bar + currentOrg string // current organization for title bar } // Login message types @@ -5584,7 +5586,7 @@ type ( loginOrgSubmittedMsg struct{} ) -func newLoginModel(homeDir string) loginModel { +func newLoginModel(homeDir, currentUsername, currentOrg string) loginModel { s := spinner.New() s.Spinner = spinner.Dot s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("12")) @@ -5597,12 +5599,14 @@ func newLoginModel(homeDir string) loginModel { ti.PromptStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("12")) return loginModel{ - spinner: s, - textInput: ti, - status: "waiting", - homeDir: homeDir, - width: 80, - height: 24, + spinner: s, + textInput: ti, + status: "waiting", + homeDir: homeDir, + width: 80, + height: 24, + currentUsername: currentUsername, + currentOrg: currentOrg, } } @@ -5616,7 +5620,7 @@ func (m loginModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { - case "ctrl+c": + case "ctrl+c", "esc": m.done = true return m, tea.Quit case "enter": @@ -5728,24 +5732,22 @@ func (m loginModel) renderWaitingView() string { } innerWidth := maxContentWidth - 2 - b.WriteString(renderTitleBar("๐Ÿ”ง Setup", "", "", innerWidth) + "\n") - b.WriteString("\n") - b.WriteString("๐Ÿ” GitHub Authentication (OAuth)\n") + b.WriteString(renderTitleBar("๐Ÿ”ง Setup / โœจ Login with code", m.currentUsername, m.currentOrg, innerWidth) + "\n") b.WriteString("\n") if m.userCode == "" { b.WriteString(m.spinner.View() + " Requesting device code...\n") } else { - b.WriteString("1. Opening browser to: github.com/login/device\n") + b.WriteString("1. Opening browser to https://github.com/login/device\n") b.WriteString("\n") b.WriteString("2. Enter this code:\n") b.WriteString("\n") - // Code box with margin for alignment + // Code box with larger padding for emphasis codeStyle := lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(lipgloss.Color("12")). - Padding(0, 3). + Padding(0, 4). Bold(true). MarginLeft(3) @@ -5755,7 +5757,7 @@ func (m loginModel) renderWaitingView() string { } b.WriteString("\n") - b.WriteString("Press Ctrl+C to cancel\n") + b.WriteString("Press Esc to cancel\n") b.WriteString("\n") return b.String() @@ -5833,14 +5835,14 @@ func (m loginModel) renderErrorView() string { } // RunLogin runs the OAuth device flow login -func RunLogin(homeDir string) error { +func RunLogin(homeDir, currentUsername, currentOrg string) error { // Ensure home directory exists if err := os.MkdirAll(homeDir, 0755); err != nil { return fmt.Errorf("failed to create home directory: %w", err) } // Create the Bubble Tea model - m := newLoginModel(homeDir) + m := newLoginModel(homeDir, currentUsername, currentOrg) p := tea.NewProgram(m, tea.WithAltScreen()) // Run the device flow in a goroutine @@ -6015,7 +6017,7 @@ func RunSetupMenu(homeDir, username, organization string) error { } if sm.runOAuth { - if err := RunLogin(homeDir); err != nil { + if err := RunLogin(homeDir, username, organization); err != nil { slog.Error("OAuth login failed", "error", err) } // Reload .env after login diff --git a/main.md b/main.md index 4c38bb6..d702d3c 100644 --- a/main.md +++ b/main.md @@ -199,21 +199,40 @@ The app uses a registered OAuth App for authentication: ``` โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ - โ”‚ GitHub ๐Ÿง  Login โ”‚ + โ”‚ GitHub Brain / ๐Ÿ”ง Setup / โœจ Login with code 1.0.0 โ”‚ + โ”‚ โ”‚ + โ”‚ 1. Opening browser to https://github.com/login/device โ”‚ + โ”‚ โ”‚ + โ”‚ 2. Enter this code: โ”‚ + โ”‚ โ”‚ + โ”‚ โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ + โ”‚ โ”‚ F934-7E83 โ”‚ โ”‚ + โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ”‚ + โ”‚ โ”‚ + โ”‚ โ ‹ Waiting for authorization... โ”‚ โ”‚ โ”‚ - โ”‚ ๐Ÿ” GitHub Authentication (OAuth) โ”‚ + โ”‚ Press Esc to cancel โ”‚ + โ”‚ โ”‚ + โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + ``` + + With user logged in (and organization configured): + + ``` + โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ + โ”‚ GitHub Brain / ๐Ÿ”ง Setup / โœจ Login with code ๐Ÿ‘ค @wham ยท ๐Ÿข my-org ยท 1.0.0 โ”‚ โ”‚ โ”‚ - โ”‚ 1. Opening browser to: github.com/login/device โ”‚ + โ”‚ 1. Opening browser to https://github.com/login/device โ”‚ โ”‚ โ”‚ โ”‚ 2. Enter this code: โ”‚ โ”‚ โ”‚ - โ”‚ โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ - โ”‚ โ”‚ ABCD-1234 โ”‚ โ”‚ - โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ”‚ + โ”‚ โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ + โ”‚ โ”‚ F934-7E83 โ”‚ โ”‚ + โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ”‚ โ”‚ โ”‚ โ”‚ โ ‹ Waiting for authorization... โ”‚ โ”‚ โ”‚ - โ”‚ Press Ctrl+C to cancel โ”‚ + โ”‚ Press Esc to cancel โ”‚ โ”‚ โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ``` From 8efe0acc46f59641ca812b5aab8e581d2fd6b05e Mon Sep 17 00:00:00 2001 From: Tomas Vesely <448809+wham@users.noreply.github.com> Date: Wed, 31 Dec 2025 14:13:23 -0800 Subject: [PATCH 2/5] Refactor login flow and update UI text for clarity --- main.go | 40 ++++++++++++++++++++++++++++++++++++---- main.md | 12 ++++++------ 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/main.go b/main.go index cf9100f..1fbae39 100644 --- a/main.go +++ b/main.go @@ -5136,6 +5136,9 @@ func RunMainTUI(homeDir string) error { if mm.runSetup { if err := RunSetupMenu(homeDir, mm.username, mm.organization); err != nil { + if err.Error() == "quit" { + return nil // Exit app cleanly + } // Log error but continue to menu slog.Error("Setup failed", "error", err) } @@ -5620,7 +5623,12 @@ func (m loginModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { - case "ctrl+c", "esc": + case "ctrl+c": + m.status = "quit" + m.done = true + return m, tea.Quit + case "esc": + m.status = "cancelled" m.done = true return m, tea.Quit case "enter": @@ -5732,7 +5740,7 @@ func (m loginModel) renderWaitingView() string { } innerWidth := maxContentWidth - 2 - b.WriteString(renderTitleBar("๐Ÿ”ง Setup / โœจ Login with code", m.currentUsername, m.currentOrg, innerWidth) + "\n") + b.WriteString(renderTitleBar("๐Ÿ”ง Setup / โœจ Login with device", m.currentUsername, m.currentOrg, innerWidth) + "\n") b.WriteString("\n") if m.userCode == "" { @@ -5856,9 +5864,15 @@ func RunLogin(homeDir, currentUsername, currentOrg string) error { // Check if login was successful if lm, ok := finalModel.(loginModel); ok { + if lm.status == "quit" { + return fmt.Errorf("quit") + } if lm.status == "error" { return fmt.Errorf("%s", lm.errorMsg) } + if lm.status == "cancelled" { + return nil // Go back without error + } if lm.status != "success" { return fmt.Errorf("login cancelled") } @@ -5893,7 +5907,7 @@ func newSetupMenuModel(homeDir, username, organization string) setupMenuModel { username: username, organization: organization, choices: []menuChoice{ - {icon: "โœจ", name: "Login with code", description: "Recommended for organization owners"}, + {icon: "โœจ", name: "Login with device", description: "Recommended for organization owners"}, {icon: "๐Ÿ”‘", name: "Login with PAT", description: "Works without organization ownership"}, {icon: "๐Ÿ“", name: "Advanced", description: "Edit configuration file"}, {icon: "๐Ÿ”™", name: "Back", description: "Esc"}, @@ -6012,12 +6026,19 @@ func RunSetupMenu(homeDir, username, organization string) error { return fmt.Errorf("unexpected model type") } - if sm.quitting || sm.goBack { + if sm.quitting { + return fmt.Errorf("quit") + } + + if sm.goBack { return nil } if sm.runOAuth { if err := RunLogin(homeDir, username, organization); err != nil { + if err.Error() == "quit" { + return err // Propagate quit to exit app + } slog.Error("OAuth login failed", "error", err) } // Reload .env after login @@ -6028,6 +6049,9 @@ func RunSetupMenu(homeDir, username, organization string) error { if sm.runPAT { if err := RunPATLogin(homeDir); err != nil { + if err.Error() == "quit" { + return err // Propagate quit to exit app + } slog.Error("PAT login failed", "error", err) } // Reload .env after login @@ -6140,9 +6164,11 @@ func (m patLoginModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.KeyMsg: switch msg.String() { case "ctrl+c": + m.status = "quit" m.done = true return m, tea.Quit case "esc": + m.status = "cancelled" m.done = true return m, tea.Quit case "enter": @@ -6361,9 +6387,15 @@ func RunPATLogin(homeDir string) error { // Check if login was successful if pm, ok := finalModel.(patLoginModel); ok { + if pm.status == "quit" { + return fmt.Errorf("quit") + } if pm.status == "error" { return fmt.Errorf("%s", pm.errorMsg) } + if pm.status == "cancelled" { + return nil // Go back without error + } if pm.status != "success" { return fmt.Errorf("login cancelled") } diff --git a/main.md b/main.md index d702d3c..46e130c 100644 --- a/main.md +++ b/main.md @@ -82,8 +82,8 @@ Right side components (shown only when available): - Use arrow keys (โ†‘/โ†“) or j/k to navigate - Press Enter to select -- Press Esc to go back (in submenus) -- Press Ctrl+C to quit +- Press Esc to go back one screen (in submenus and dialogs) +- Press Ctrl+C to exit the app from any screen - Highlight current selection with `โ–ถ` (blue) ### Menu Items @@ -113,7 +113,7 @@ The Setup submenu provides authentication and configuration options: โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ GitHub Brain / ๐Ÿ”ง Setup ๐Ÿ‘ค Not logged in โ”‚ โ”‚ โ”‚ -โ”‚ โ–ถ โœจ Login with code Recommended for organization owners โ”‚ +โ”‚ โ–ถ โœจ Login with device Recommended for organization owners โ”‚ โ”‚ ๐Ÿ”‘ Login with PAT Works without organization ownership โ”‚ โ”‚ ๐Ÿ“ Advanced Edit configuration file โ”‚ โ”‚ โ†ฉ๏ธ Back Esc โ”‚ @@ -122,7 +122,7 @@ The Setup submenu provides authentication and configuration options: ### Setup Menu Items -1. **โœจ Login with code** - Recommended for organization owners. Runs the OAuth device flow (see [OAuth Login](#oauth-login) section) +1. **โœจ Login with device** - Recommended for organization owners. Runs the OAuth device flow (see [OAuth Login](#oauth-login) section) 2. **๐Ÿ”‘ Login with PAT** - Works without organization ownership. Manually enter a PAT (see [PAT Login](#pat-login) section) 3. **๐Ÿ“ Advanced** - Edit configuration file `{HomeDir}/.env` 4. **โ†ฉ๏ธ Back** - Return to main menu (Esc) @@ -199,7 +199,7 @@ The app uses a registered OAuth App for authentication: ``` โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ - โ”‚ GitHub Brain / ๐Ÿ”ง Setup / โœจ Login with code 1.0.0 โ”‚ + โ”‚ GitHub Brain / ๐Ÿ”ง Setup / โœจ Login with device 1.0.0 โ”‚ โ”‚ โ”‚ โ”‚ 1. Opening browser to https://github.com/login/device โ”‚ โ”‚ โ”‚ @@ -220,7 +220,7 @@ The app uses a registered OAuth App for authentication: ``` โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ - โ”‚ GitHub Brain / ๐Ÿ”ง Setup / โœจ Login with code ๐Ÿ‘ค @wham ยท ๐Ÿข my-org ยท 1.0.0 โ”‚ + โ”‚ GitHub Brain / ๐Ÿ”ง Setup / โœจ Login with device ๐Ÿ‘ค @wham ยท ๐Ÿข my-org ยท 1.0.0 โ”‚ โ”‚ โ”‚ โ”‚ 1. Opening browser to https://github.com/login/device โ”‚ โ”‚ โ”‚ From fb7e284d6f5adb2d6f5df0c2d927aaf75b25e165 Mon Sep 17 00:00:00 2001 From: Tomas Vesely <448809+wham@users.noreply.github.com> Date: Wed, 31 Dec 2025 14:17:37 -0800 Subject: [PATCH 3/5] Update Back navigation icon to a left arrow for consistency in Setup menu --- main.go | 19 +++++++++++++++---- main.md | 4 ++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/main.go b/main.go index 1fbae39..3854e97 100644 --- a/main.go +++ b/main.go @@ -5910,7 +5910,7 @@ func newSetupMenuModel(homeDir, username, organization string) setupMenuModel { {icon: "โœจ", name: "Login with device", description: "Recommended for organization owners"}, {icon: "๐Ÿ”‘", name: "Login with PAT", description: "Works without organization ownership"}, {icon: "๐Ÿ“", name: "Advanced", description: "Edit configuration file"}, - {icon: "๐Ÿ”™", name: "Back", description: "Esc"}, + {icon: "โ†", name: "Back", description: "Esc"}, }, cursor: 0, width: 80, @@ -5984,6 +5984,15 @@ func (m setupMenuModel) View() string { // Menu items - same format as Home screen selectorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("12")) // Blue selector + + // Find the longest name for alignment + maxNameWidth := 0 + for _, choice := range m.choices { + if len(choice.name) > maxNameWidth { + maxNameWidth = len(choice.name) + } + } + for i, choice := range m.choices { cursor := " " descStyle := dimStyle @@ -5991,10 +6000,12 @@ func (m setupMenuModel) View() string { cursor = selectorStyle.Render("โ–ถ") + " " descStyle = selectedStyle } - // Pad name to 15 characters for alignment - paddedName := fmt.Sprintf("%-15s", choice.name) + // Pad icon to 2 characters (emoji width) and name for alignment + iconWidth := lipgloss.Width(choice.icon) + iconPadding := strings.Repeat(" ", 2-iconWidth) + paddedName := fmt.Sprintf("%-*s", maxNameWidth, choice.name) // Name is always bold (titleStyle), description uses current selection style - b.WriteString(fmt.Sprintf("%s%s %s %s", cursor, choice.icon, titleStyle.Render(paddedName), descStyle.Render(choice.description))) + b.WriteString(fmt.Sprintf("%s%s%s %s %s", cursor, choice.icon, iconPadding, titleStyle.Render(paddedName), descStyle.Render(choice.description))) if i < len(m.choices)-1 { b.WriteString("\n\n") } diff --git a/main.md b/main.md index 46e130c..b5958f6 100644 --- a/main.md +++ b/main.md @@ -116,7 +116,7 @@ The Setup submenu provides authentication and configuration options: โ”‚ โ–ถ โœจ Login with device Recommended for organization owners โ”‚ โ”‚ ๐Ÿ”‘ Login with PAT Works without organization ownership โ”‚ โ”‚ ๐Ÿ“ Advanced Edit configuration file โ”‚ -โ”‚ โ†ฉ๏ธ Back Esc โ”‚ +โ”‚ โ† Back Esc โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ``` @@ -125,7 +125,7 @@ The Setup submenu provides authentication and configuration options: 1. **โœจ Login with device** - Recommended for organization owners. Runs the OAuth device flow (see [OAuth Login](#oauth-login) section) 2. **๐Ÿ”‘ Login with PAT** - Works without organization ownership. Manually enter a PAT (see [PAT Login](#pat-login) section) 3. **๐Ÿ“ Advanced** - Edit configuration file `{HomeDir}/.env` -4. **โ†ฉ๏ธ Back** - Return to main menu (Esc) +4. **โ† Back** - Return to main menu (Esc) ### Open Configuration File (Advanced) From 53d204d61a0fd1b1733ad83349438163b7f0bf85 Mon Sep 17 00:00:00 2001 From: Tomas Vesely <448809+wham@users.noreply.github.com> Date: Wed, 31 Dec 2025 14:38:13 -0800 Subject: [PATCH 4/5] Enhance testing capabilities by adding --test flag to run script and updating SKILL.md; refactor menu models to remember cursor position --- .github/skills/testing/SKILL.md | 7 +++--- main.go | 41 +++++++++++++++++++++++---------- main.md | 12 ++++++---- scripts/run | 25 +++++++++++++++----- 4 files changed, 60 insertions(+), 25 deletions(-) diff --git a/.github/skills/testing/SKILL.md b/.github/skills/testing/SKILL.md index 89087ad..7c9c2c3 100644 --- a/.github/skills/testing/SKILL.md +++ b/.github/skills/testing/SKILL.md @@ -18,9 +18,10 @@ Use this skill: ## Starting the application -- Run `scripts/run` where the user would normally run `github-brain` - - `scripts/run pull` equivalently runs `github-brain pull` - - `scripts/run mcp` equivalently runs `github-brain mcp` +- Run `scripts/run --test` where the user would normally run `github-brain` + - `scripts/run --test pull` equivalently runs `github-brain pull` + - `scripts/run --test mcp` equivalently runs `github-brain mcp` + - The `--test` flag runs `go vet` before building to catch issues early - Ensure `.env` files is configured to use the `github-brain-test` organization - Use GitHub MCP to add new issue/PRs/discussions as needed for testing - Simulate user input: Send key presses, control combinations, or specific commands to the running application. diff --git a/main.go b/main.go index 3854e97..21c3153 100644 --- a/main.go +++ b/main.go @@ -4960,7 +4960,7 @@ type authCheckResultMsg struct { organization string } -func newMainMenuModel(homeDir string) mainMenuModel { +func newMainMenuModel(homeDir string, cursor int) mainMenuModel { return mainMenuModel{ homeDir: homeDir, choices: []menuChoice{ @@ -4968,7 +4968,7 @@ func newMainMenuModel(homeDir string) mainMenuModel { {icon: "๐Ÿ”ง", name: "Setup", description: "Configure GitHub username and organization"}, {icon: "๐Ÿšช", name: "Exit", description: "Ctrl+C"}, }, - cursor: 0, + cursor: cursor, status: "Checking authentication...", width: 80, height: 24, @@ -5116,8 +5116,9 @@ func RunMainTUI(homeDir string) error { return fmt.Errorf("failed to create home directory: %w", err) } + cursor := 0 // Remember cursor position across menu returns for { - m := newMainMenuModel(homeDir) + m := newMainMenuModel(homeDir, cursor) p := tea.NewProgram(m, tea.WithAltScreen()) finalModel, err := p.Run() @@ -5130,6 +5131,8 @@ func RunMainTUI(homeDir string) error { return fmt.Errorf("unexpected model type") } + cursor = mm.cursor // Remember cursor position + if mm.quitting { return nil } @@ -5632,6 +5635,11 @@ func (m loginModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.done = true return m, tea.Quit case "enter": + if m.status == "waiting" { + m.status = "cancelled" + m.done = true + return m, tea.Quit + } if m.status == "org_input" { m.organization = strings.TrimSpace(m.textInput.Value()) return m, func() tea.Msg { return loginOrgSubmittedMsg{} } @@ -5747,14 +5755,16 @@ func (m loginModel) renderWaitingView() string { b.WriteString(m.spinner.View() + " Requesting device code...\n") } else { b.WriteString("1. Opening browser to https://github.com/login/device\n") + b.WriteString("2. Grant access to all organizations you are planning to use\n") b.WriteString("\n") - b.WriteString("2. Enter this code:\n") + b.WriteString("3. Enter this code:\n") b.WriteString("\n") - // Code box with larger padding for emphasis + // Code box with double border - gold/yellow stands out against purple codeStyle := lipgloss.NewStyle(). - Border(lipgloss.RoundedBorder()). - BorderForeground(lipgloss.Color("12")). + Border(lipgloss.DoubleBorder()). + BorderForeground(lipgloss.Color("220")). + Foreground(lipgloss.Color("220")). Padding(0, 4). Bold(true). MarginLeft(3) @@ -5765,8 +5775,12 @@ func (m loginModel) renderWaitingView() string { } b.WriteString("\n") - b.WriteString("Press Esc to cancel\n") - b.WriteString("\n") + + // Back menu item - always selected, same format as Setup screen + selectorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("12")) + selectedStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("12")).Bold(true) + paddedName := fmt.Sprintf("%-4s", "Back") + b.WriteString(selectorStyle.Render("โ–ถ") + " โ† " + titleStyle.Render(paddedName) + " " + selectedStyle.Render("Esc")) return b.String() } @@ -5901,7 +5915,7 @@ type setupMenuModel struct { goBack bool } -func newSetupMenuModel(homeDir, username, organization string) setupMenuModel { +func newSetupMenuModel(homeDir, username, organization string, cursor int) setupMenuModel { return setupMenuModel{ homeDir: homeDir, username: username, @@ -5912,7 +5926,7 @@ func newSetupMenuModel(homeDir, username, organization string) setupMenuModel { {icon: "๐Ÿ“", name: "Advanced", description: "Edit configuration file"}, {icon: "โ†", name: "Back", description: "Esc"}, }, - cursor: 0, + cursor: cursor, width: 80, height: 24, } @@ -6023,8 +6037,9 @@ func (m setupMenuModel) View() string { // RunSetupMenu runs the setup submenu func RunSetupMenu(homeDir, username, organization string) error { + cursor := 0 // Remember cursor position across menu returns for { - m := newSetupMenuModel(homeDir, username, organization) + m := newSetupMenuModel(homeDir, username, organization, cursor) p := tea.NewProgram(m, tea.WithAltScreen()) finalModel, err := p.Run() @@ -6037,6 +6052,8 @@ func RunSetupMenu(homeDir, username, organization string) error { return fmt.Errorf("unexpected model type") } + cursor = sm.cursor // Remember cursor position + if sm.quitting { return fmt.Errorf("quit") } diff --git a/main.md b/main.md index b5958f6..185ca1d 100644 --- a/main.md +++ b/main.md @@ -85,16 +85,18 @@ Right side components (shown only when available): - Press Esc to go back one screen (in submenus and dialogs) - Press Ctrl+C to exit the app from any screen - Highlight current selection with `โ–ถ` (blue) +- When going back, remember and restore the previous cursor position ### Menu Items -1. **๏ฟฝ Pull** - Runs the pull operation (see [pull](#pull) section) +1. **๐Ÿ”„ Pull** - Runs the pull operation (see [pull](#pull) section) 2. **๐Ÿ”ง Setup** - Opens the setup submenu (see [Setup Menu](#setup-menu) section) 3. **๐Ÿšช Exit** - Exit the application (Ctrl+C) ### Default Selection -- Always start with **Pull** selected (the first item) +- Start with **Pull** selected on first launch +- When returning from a submenu, restore the previous selection ### Flow @@ -202,8 +204,9 @@ The app uses a registered OAuth App for authentication: โ”‚ GitHub Brain / ๐Ÿ”ง Setup / โœจ Login with device 1.0.0 โ”‚ โ”‚ โ”‚ โ”‚ 1. Opening browser to https://github.com/login/device โ”‚ + โ”‚ 2. Grant access to all organizations you are planning to use โ”‚ โ”‚ โ”‚ - โ”‚ 2. Enter this code: โ”‚ + โ”‚ 3. Enter this code: โ”‚ โ”‚ โ”‚ โ”‚ โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ โ”‚ โ”‚ F934-7E83 โ”‚ โ”‚ @@ -223,8 +226,9 @@ The app uses a registered OAuth App for authentication: โ”‚ GitHub Brain / ๐Ÿ”ง Setup / โœจ Login with device ๐Ÿ‘ค @wham ยท ๐Ÿข my-org ยท 1.0.0 โ”‚ โ”‚ โ”‚ โ”‚ 1. Opening browser to https://github.com/login/device โ”‚ + โ”‚ 2. Grant access to all organizations you are planning to use โ”‚ โ”‚ โ”‚ - โ”‚ 2. Enter this code: โ”‚ + โ”‚ 3. Enter this code: โ”‚ โ”‚ โ”‚ โ”‚ โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ โ”‚ โ”‚ F934-7E83 โ”‚ โ”‚ diff --git a/scripts/run b/scripts/run index 2eb8312..47cd0bf 100755 --- a/scripts/run +++ b/scripts/run @@ -2,9 +2,22 @@ set -e cd "$(dirname "$0")/.." -# Run go vet (non-blocking in development) -echo "Running go vet..." -go vet ./... || echo "Warning: go vet found issues (non-blocking in development)" +# Check for --test flag +TEST_MODE=false +ARGS=() +for arg in "$@"; do + if [ "$arg" = "--test" ]; then + TEST_MODE=true + else + ARGS+=("$arg") + fi +done + +# Run go vet only in test mode +if [ "$TEST_MODE" = true ]; then + echo "Running go vet..." + go vet ./... || echo "Warning: go vet found issues" +fi # Build with FTS5 support enabled CGO_ENABLED=1 CGO_CFLAGS="-DSQLITE_ENABLE_FTS5" CGO_LDFLAGS="-Wl,-no_warn_duplicate_libraries" go build -gcflags="all=-N -l" -o ./build/github-brain . @@ -14,7 +27,7 @@ CHECKOUT_DIR="$(pwd)" # Check if -m flag is already provided HAS_M_FLAG=false -for arg in "$@"; do +for arg in "${ARGS[@]}"; do if [ "$arg" = "-m" ]; then HAS_M_FLAG=true break @@ -23,7 +36,7 @@ done # Add -m flag only if not already provided if [ "$HAS_M_FLAG" = false ]; then - ./build/github-brain "$@" -m "$CHECKOUT_DIR" + ./build/github-brain "${ARGS[@]}" -m "$CHECKOUT_DIR" else - ./build/github-brain "$@" + ./build/github-brain "${ARGS[@]}" fi \ No newline at end of file From eeb9b06d88dde32fc4befddafd24617063bb1f1a Mon Sep 17 00:00:00 2001 From: Tomas Vesely <448809+wham@users.noreply.github.com> Date: Wed, 31 Dec 2025 14:44:22 -0800 Subject: [PATCH 5/5] Update login instructions for clarity and consistency in authorization steps --- main.go | 5 +++-- main.md | 12 ++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/main.go b/main.go index 21c3153..d6461dd 100644 --- a/main.go +++ b/main.go @@ -5755,9 +5755,8 @@ func (m loginModel) renderWaitingView() string { b.WriteString(m.spinner.View() + " Requesting device code...\n") } else { b.WriteString("1. Opening browser to https://github.com/login/device\n") - b.WriteString("2. Grant access to all organizations you are planning to use\n") b.WriteString("\n") - b.WriteString("3. Enter this code:\n") + b.WriteString("2. Enter this code:\n") b.WriteString("\n") // Code box with double border - gold/yellow stands out against purple @@ -5771,6 +5770,8 @@ func (m loginModel) renderWaitingView() string { b.WriteString(codeStyle.Render(m.userCode) + "\n") b.WriteString("\n") + b.WriteString("3. Grant access to the organizations you are planning to use with GitHub Brain\n") + b.WriteString("\n") b.WriteString(m.spinner.View() + " Waiting for authorization...\n") } diff --git a/main.md b/main.md index 185ca1d..9124426 100644 --- a/main.md +++ b/main.md @@ -204,14 +204,16 @@ The app uses a registered OAuth App for authentication: โ”‚ GitHub Brain / ๐Ÿ”ง Setup / โœจ Login with device 1.0.0 โ”‚ โ”‚ โ”‚ โ”‚ 1. Opening browser to https://github.com/login/device โ”‚ - โ”‚ 2. Grant access to all organizations you are planning to use โ”‚ โ”‚ โ”‚ - โ”‚ 3. Enter this code: โ”‚ + โ”‚ 2. Enter this code: โ”‚ โ”‚ โ”‚ โ”‚ โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ โ”‚ โ”‚ F934-7E83 โ”‚ โ”‚ โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ”‚ โ”‚ โ”‚ + โ”‚ 3. Grant access to the organizations you are planning to use โ”‚ + โ”‚ with GitHub Brain โ”‚ + โ”‚ โ”‚ โ”‚ โ ‹ Waiting for authorization... โ”‚ โ”‚ โ”‚ โ”‚ Press Esc to cancel โ”‚ @@ -226,14 +228,16 @@ The app uses a registered OAuth App for authentication: โ”‚ GitHub Brain / ๐Ÿ”ง Setup / โœจ Login with device ๐Ÿ‘ค @wham ยท ๐Ÿข my-org ยท 1.0.0 โ”‚ โ”‚ โ”‚ โ”‚ 1. Opening browser to https://github.com/login/device โ”‚ - โ”‚ 2. Grant access to all organizations you are planning to use โ”‚ โ”‚ โ”‚ - โ”‚ 3. Enter this code: โ”‚ + โ”‚ 2. Enter this code: โ”‚ โ”‚ โ”‚ โ”‚ โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ โ”‚ โ”‚ F934-7E83 โ”‚ โ”‚ โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ”‚ โ”‚ โ”‚ + โ”‚ 3. Grant access to the organizations you are planning to use โ”‚ + โ”‚ with GitHub Brain โ”‚ + โ”‚ โ”‚ โ”‚ โ ‹ Waiting for authorization... โ”‚ โ”‚ โ”‚ โ”‚ Press Esc to cancel โ”‚