Skip to content

Commit b70612f

Browse files
committed
review fixes for v0.63.0
Caught during self-review of the nine polish items: 1. Recovery notice handled negative age ("-1 min ago") from clock skew or very fresh snapshots. Added "just now" branch for age < 1 minute. 2. Shortcuts overlay Diff Preview entries were invented: - Enter was documented as "Apply" but diff actually uses y / Y - "r: Reject" never existed (diff uses n / N / Esc) - "Tab: Toggle context lines" is wrong — Tab only toggles focus in the multi-file variant, and is unbound in single-file diff - "e: Edit before applying" was aspirational — no key handler exists Replaced with the real bindings: y, n/Esc, A, R, Tab (multi-only). 3. Error guidance regexes used bare HTTP status codes (401, 500, etc.) which would match "500ms latency" or any substring. Added \b word boundaries; 5xx now also requires a nearby keyword (error/server/ gateway/unavailable/timeout) so random numbers don't trigger. 4. Tool progress bar truncation sliced by bytes, not runes. For Cyrillic or emoji output this could produce invalid UTF-8 before appending "…". Now converts to []rune before slicing.
1 parent 5e060e0 commit b70612f

4 files changed

Lines changed: 20 additions & 11 deletions

File tree

internal/app/app.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,9 @@ func (a *App) Run() error {
434434
age := time.Since(snap.Timestamp).Round(time.Minute)
435435
var ageStr string
436436
switch {
437+
case age < time.Minute:
438+
// Clock skew or very fresh snapshot — avoid "-1 min ago"
439+
ageStr = "just now"
437440
case age < time.Hour:
438441
ageStr = fmt.Sprintf("%d min ago", int(age.Minutes()))
439442
case age < 24*time.Hour:

internal/ui/error_guidance.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -274,23 +274,24 @@ var errorGuidancePatterns = []ErrorGuidance{
274274
Suggestions: []string{"The conversation is too long for this model", "Run /clear to start fresh, or /compact to summarize older messages", "Switch to a model with a larger context window via /model"},
275275
Command: "/compact",
276276
},
277-
// Authentication / API key
277+
// Authentication / API key — use word boundaries on status codes so we
278+
// don't match "401ms latency" etc.
278279
{
279-
Pattern: regexp.MustCompile(`(?i)(invalid api key|authentication failed|unauthorized|401|403.*forbidden|api key.*invalid|authentication_error)`),
280+
Pattern: regexp.MustCompile(`(?i)(invalid api key|authentication failed|unauthorized|\b401\b|\b403\b.*forbidden|api key.*invalid|authentication_error)`),
280281
Title: "Authentication Failed",
281282
Suggestions: []string{"Your API key is missing, invalid, or expired", "Run /auth to re-authenticate, or check your config file", "Verify the key at the provider's dashboard"},
282283
Command: "/auth",
283284
},
284285
// Quota / billing
285286
{
286-
Pattern: regexp.MustCompile(`(?i)(quota.*exceed|insufficient.*credit|billing|payment.*required|402)`),
287+
Pattern: regexp.MustCompile(`(?i)(quota.*exceed|insufficient.*credit|billing|payment.*required|\b402\b)`),
287288
Title: "Quota or Billing Issue",
288289
Suggestions: []string{"Your account has hit a usage cap or billing issue", "Check your provider dashboard for billing status", "Try a different provider via /model"},
289290
Command: "",
290291
},
291-
// Server errors (5xx)
292+
// Server errors (5xx) — word boundaries so "500ms" doesn't trigger
292293
{
293-
Pattern: regexp.MustCompile(`(?i)(500|502|503|504|internal server error|bad gateway|service unavailable|gateway timeout|overloaded|temporarily unavailable)`),
294+
Pattern: regexp.MustCompile(`(?i)(\b5\d\d\b.*(error|server|gateway|unavailable|timeout)|internal server error|bad gateway|service unavailable|gateway timeout|overloaded|temporarily unavailable)`),
294295
Title: "Provider Server Error",
295296
Suggestions: []string{"The provider is experiencing issues — this is not your fault", "The retry logic already tried automatically", "Switch providers with /model, or try again in a few minutes"},
296297
Command: "",

internal/ui/shortcuts.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,11 @@ func DefaultShortcuts() []ShortcutCategory {
6565
{
6666
Name: "Diff Preview",
6767
Shortcuts: []Shortcut{
68-
{Keys: []string{"Enter"}, Description: "Apply diff"},
69-
{Keys: []string{"r"}, Description: "Reject diff"},
70-
{Keys: []string{"Tab"}, Description: "Toggle context lines"},
68+
{Keys: []string{"y"}, Description: "Apply this diff"},
69+
{Keys: []string{"n", "Esc"}, Description: "Reject this diff"},
70+
{Keys: []string{"A"}, Description: "Apply all remaining diffs"},
71+
{Keys: []string{"R"}, Description: "Reject all remaining diffs"},
72+
{Keys: []string{"Tab"}, Description: "Switch focus (multi-file only)"},
7173
},
7274
},
7375
{

internal/ui/tool_progress_bar.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,12 @@ func (m *ToolProgressBarModel) View(width int) string {
156156
// non-empty line so the user sees live progress instead of stale head.
157157
if m.currentStep != "" {
158158
step := lastNonEmptyLine(m.currentStep)
159-
maxLen := 60
160-
if len(step) > maxLen {
161-
step = step[:maxLen-1] + "…"
159+
// Rune-aware truncation so multibyte chars (emoji, Cyrillic) don't
160+
// get sliced mid-byte and render as replacement chars.
161+
const maxRunes = 60
162+
runes := []rune(step)
163+
if len(runes) > maxRunes {
164+
step = string(runes[:maxRunes-1]) + "…"
162165
}
163166
builder.WriteString(" ")
164167
builder.WriteString(dimStyle.Render(step))

0 commit comments

Comments
 (0)