refactor(tui): deduplicate shared utilities and simplify flow runners#22
refactor(tui): deduplicate shared utilities and simplify flow runners#22
Conversation
- Consolidate three identical token masking functions into shared maskTokenPreview - Extract duration formatting into shared FormatDurationCompact/Human/Interval - Extract doTokenExchange helper to deduplicate HTTP token exchange logic - Unify OAuth error code maps with browser-specific overrides - Extract generic runFlow and handleFlowUpdate to deduplicate flow runners - Remove unused exported methods IsHidden, ViewSimple, ViewCompact - Fix goroutine leak risk in browser flow timer Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
Refactors the TUI/auth flow implementation to reduce duplicated utilities and consolidate shared flow logic, while also centralizing OAuth error messaging and token exchange HTTP handling.
Changes:
- Deduplicates token masking + duration/interval formatting into shared TUI utilities (
tui/styles.go). - Extracts shared flow runner logic in Bubble Tea manager (
runFlow/handleFlowUpdate) to unify browser + device runners. - Centralizes OAuth token exchange HTTP logic (
doTokenExchange) and unifies OAuth error-code messaging between browser and TUI paths.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tui/unified_flow_view.go | Switches expiry rendering to shared duration formatter. |
| tui/token_view.go | Removes local masking/duration helpers; uses shared utilities and simplifies backend string parsing. |
| tui/styles.go | Adds shared duration/interval formatters and hosts token preview masking helper. |
| tui/simple_manager.go | Uses shared token preview masking helper. |
| tui/flow_renderer.go | Switches expiry rendering to shared duration formatter. |
| tui/error_parser.go | Introduces shared OAuth error message map and uses it in TUI sanitization. |
| tui/device_view.go | Switches interval formatting to shared helper and removes now-unused local helper/import. |
| tui/components/timer.go | Removes compact timer view API. |
| tui/components/step_indicator.go | Removes compact step indicator view API. |
| tui/components/progress_bar.go | Removes compact progress bar view API. |
| tui/components/info_box.go | Removes simple info box rendering API. |
| tui/components/help_view.go | Removes IsHidden() API. |
| tui/components/components_test.go | Removes tests for deleted compact/simple component views. |
| tui/bubbletea_manager.go | Refactors browser/device flow runners into shared runFlow + handleFlowUpdate. |
| tokens.go | Adds shared doTokenExchange + tokenResponseToCredstore helpers for OAuth token POST handling. |
| main.go | Uses doTokenExchange during refresh; keeps refresh-token preservation logic. |
| error_sanitizer.go | Uses shared TUI OAuth error messages with browser-specific overrides. |
| browser_flow.go | Uses shared token exchange helper and improves timer goroutine cancellation handling. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tokens.go
Outdated
| return nil, hookErr | ||
| } | ||
| } | ||
| return nil, fmt.Errorf("%s: %s", errResp.Error, errResp.ErrorDescription) |
There was a problem hiding this comment.
doTokenExchange formats OAuth errors as "%s: %s". If error_description is empty (allowed by the spec / common in some servers), this returns a trailing ": ", which then shows up in user-facing errors and logs. Consider omitting the separator when the description is empty (or falling back to a generic message) to avoid awkward output.
| return nil, fmt.Errorf("%s: %s", errResp.Error, errResp.ErrorDescription) | |
| desc := strings.TrimSpace(errResp.ErrorDescription) | |
| if desc == "" { | |
| return nil, fmt.Errorf("%s", errResp.Error) | |
| } | |
| return nil, fmt.Errorf("%s: %s", errResp.Error, desc) |
tui/styles.go
Outdated
| d = d.Round(time.Second) | ||
| minutes := int(d.Minutes()) | ||
| seconds := int(d.Seconds()) % 60 | ||
| return fmt.Sprintf("%d:%02d", minutes, seconds) |
There was a problem hiding this comment.
FormatDurationCompact doesn’t handle negative durations correctly: int(d.Seconds()) % 60 is negative for negative inputs, producing outputs like -1:-05. Since this function is exported, it should behave sensibly for negative values (e.g., clamp to 0, or format with a leading - and absolute seconds).
| d = d.Round(time.Second) | |
| minutes := int(d.Minutes()) | |
| seconds := int(d.Seconds()) % 60 | |
| return fmt.Sprintf("%d:%02d", minutes, seconds) | |
| sign := "" | |
| if d < 0 { | |
| sign = "-" | |
| d = -d | |
| } | |
| d = d.Round(time.Second) | |
| totalSeconds := int(d.Seconds()) | |
| minutes := totalSeconds / 60 | |
| seconds := totalSeconds % 60 | |
| return fmt.Sprintf("%s%d:%02d", sign, minutes, seconds) |
tui/error_parser.go
Outdated
| // OAuthErrorMessages maps standard OAuth error codes to user-friendly TUI messages. | ||
| // This is the authoritative map; other packages should delegate here. | ||
| var OAuthErrorMessages = map[string]string{ | ||
| "access_denied": "Authorization was denied. You may have cancelled the request or don't have permission to access this resource.", | ||
| "invalid_request": "The authorization request was invalid. Please check your configuration.", | ||
| "unauthorized_client": "The client is not authorized to use this authorization method. Please verify your client ID and permissions.", | ||
| "invalid_client": "Client authentication failed. Please check your client ID and secret.", | ||
| "invalid_grant": "The authorization code or refresh token is invalid or expired.", | ||
| "unsupported_grant_type": "The authorization grant type is not supported by this server.", | ||
| "invalid_scope": "One or more requested scopes are invalid or not allowed.", | ||
| "server_error": "The authorization server encountered an error. Please try again later.", | ||
| "temporarily_unavailable": "The authorization server is temporarily unavailable. Please try again in a few moments.", | ||
| } |
There was a problem hiding this comment.
OAuthErrorMessages is exported as a mutable map[string]string. Any importing package can modify it at runtime, which can lead to hard-to-debug behavior and makes the “authoritative map” claim unenforceable. Consider keeping the map unexported and exposing a read-only accessor (e.g., OAuthErrorMessage(code) (string, bool) or SanitizeOAuthErrorForTUI(...)).
| case <-ctx.Done(): | ||
| return nil, false, ctx.Err() | ||
| return nil, ctx.Err() | ||
| } |
There was a problem hiding this comment.
In runFlow, the case <-ctx.Done() branch returns immediately. That stops draining updates, so any in-flight flow goroutine that attempts to send another update can block forever (leaking the goroutine) if the buffer fills. Consider canceling flowCtx and continuing to drain/ignore updates until resultCh returns (or redesign update sends to be non-blocking on context cancellation).
- Replace direct access to the OAuth error message map with a getter function to centralize and encapsulate error handling. - Improve OAuth token exchange errors by omitting empty descriptions and trimming whitespace. - Prevent goroutine and channel leaks in the TUI flow by explicitly canceling context and draining channels on exit. - Make the OAuth error message map private and add a public accessor to enforce a single authoritative source. - Enhance duration formatting to correctly handle and display negative durations. Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Summary
tui/styles.godoTokenExchange()helper to deduplicate HTTP token exchange acrossexchangeCode,refreshAccessTokenerror_sanitizer.go) and TUI (error_parser.go) packagesrunFlow()andhandleFlowUpdate()methods to deduplicate browser/device flow runners inbubbletea_manager.goIsHidden,ViewSimple,ViewCompact) and their testsNet result: -172 lines (356 added, 528 removed) across 18 files.
Test plan
make build— builds successfullymake test— all existing tests pass (37.9% coverage main, 14.6% tui, 45.8% components)make lint— 0 lint issues🤖 Generated with Claude Code