Skip to content

Commit 94ae604

Browse files
committed
fix: show dimmed reset hint while rate-limit window is inactive
Fix: rate limits - Do not render epoch reset timestamp (1970-01-01) when API returns resetAt 0 - Inactive window reads 'Reset at: N/A start using to begin countdown', with the hint in dimmed italic (brightBlack) and N/A in label grey - Active window renders reset time and countdown in the same grey as the label - resetTime is now nullable (null when resetAt <= 0) - Add WindowLimitData unit tests covering resetAt 0, valid resetAt, remaining, and empty defaults
1 parent 38f5dc0 commit 94ae604

4 files changed

Lines changed: 86 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## Unreleased
6+
7+
### Fixes
8+
9+
- TUI page 4 (Rate Limits) no longer renders an epoch reset timestamp
10+
(`1970-01-01`) when a limit window has not started yet (API returns
11+
`resetAt: 0`). When a window has no usage yet, the reset line reads
12+
`Reset at: N/A start using to begin countdown`: the `N/A` shares the grey
13+
color of the `Reset at:` label and the hint is rendered dimmed italic
14+
(`brightBlack`, via RichText) so it does not draw attention while the
15+
countdown is inactive. Once the window has a real `resetAt`, the line renders
16+
the scheduled reset time and relative countdown in the same grey style as the
17+
label, so only the active countdown is emphasized. This mirrors the official
18+
`cmd` CLI which only shows a reset clock for a valid, future reset timestamp.
19+
520
## v1.3.2 (2026-08-04)
621

722
### Features

lib/src/services/api_client.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class WindowLimitData {
9898
resetAt: (json['resetAt'] as num?)?.toInt() ?? 0,
9999
);
100100

101-
DateTime get resetTime => DateTime.fromMillisecondsSinceEpoch(resetAt);
101+
DateTime? get resetTime => resetAt > 0 ? DateTime.fromMillisecondsSinceEpoch(resetAt) : null;
102102
double get remaining => cap - used;
103103
}
104104

lib/src/tui/app.dart

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,8 +1122,7 @@ class AppState extends State<CmdBridgeApp> {
11221122
Text('EXCEEDED', style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold)),
11231123
]),
11241124
SizedBox(height: 1),
1125-
Text(' Resets at: ${_fmtDate(c.fiveHour.resetTime)} ${_relativeTime(c.fiveHour.resetTime)}',
1126-
style: TextStyle(color: Colors.grey)),
1125+
_resetLine(c.fiveHour),
11271126
]),
11281127
));
11291128
} else {
@@ -1152,8 +1151,7 @@ class AppState extends State<CmdBridgeApp> {
11521151
Text('EXCEEDED', style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold)),
11531152
]),
11541153
SizedBox(height: 1),
1155-
Text(' Resets at: ${_fmtDate(c.weekly.resetTime)} ${_relativeTime(c.weekly.resetTime)}',
1156-
style: TextStyle(color: Colors.grey)),
1154+
_resetLine(c.weekly),
11571155
]),
11581156
));
11591157
} else {
@@ -2026,6 +2024,27 @@ class AppState extends State<CmdBridgeApp> {
20262024
return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}';
20272025
}
20282026

2027+
Component _resetLine(WindowLimitData w) {
2028+
final reset = w.resetTime;
2029+
if (reset == null) {
2030+
return RichText(
2031+
text: TextSpan(children: [
2032+
const TextSpan(
2033+
text: ' Reset at: N/A ', style: TextStyle(color: Colors.grey)),
2034+
TextSpan(
2035+
text: 'start using to begin countdown',
2036+
style: TextStyle(
2037+
color: Colors.brightBlack,
2038+
fontStyle: FontStyle.italic,
2039+
),
2040+
),
2041+
]),
2042+
);
2043+
}
2044+
return Text(' Reset at: ${_fmtDate(reset)} ${_relativeTime(reset)}',
2045+
style: TextStyle(color: Colors.grey));
2046+
}
2047+
20292048
String _relativeTime(DateTime target) {
20302049
final diff = target.difference(DateTime.now());
20312050
if (diff.isNegative) return '(overdue)';

test/api_client_test.dart

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import 'package:test/test.dart';
2+
import 'package:commandcode_bridge/src/services/api_client.dart';
3+
4+
void main() {
5+
group('WindowLimitData', () {
6+
test('resetTime is null when resetAt is 0 (no active window)', () {
7+
final w = WindowLimitData.fromJson({
8+
'used': 0,
9+
'cap': 3,
10+
'exceeded': false,
11+
'resetAt': 0,
12+
});
13+
expect(w.resetTime, isNull);
14+
});
15+
16+
test('resetTime resolves positive resetAt to a real date', () {
17+
final w = WindowLimitData.fromJson({
18+
'used': 2.13,
19+
'cap': 6,
20+
'exceeded': false,
21+
'resetAt': 1786253751340,
22+
});
23+
final rt = w.resetTime;
24+
expect(rt, isNotNull);
25+
expect(rt!.millisecondsSinceEpoch, 1786253751340);
26+
});
27+
28+
test('remaining is cap minus used', () {
29+
final w = WindowLimitData.fromJson({
30+
'used': 2.13,
31+
'cap': 6,
32+
'exceeded': false,
33+
'resetAt': 1786253751340,
34+
});
35+
expect(w.remaining, closeTo(3.87, 0.001));
36+
});
37+
38+
test('missing fields default to safe values', () {
39+
final w = WindowLimitData.fromJson({});
40+
expect(w.used, 0);
41+
expect(w.cap, 0);
42+
expect(w.exceeded, isFalse);
43+
expect(w.resetAt, 0);
44+
expect(w.resetTime, isNull);
45+
});
46+
});
47+
}

0 commit comments

Comments
 (0)