Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@ All notable changes to this fork compared to upstream
[`ClaudeCodeUsage/ClaudeCodeUsage`](https://github.com/ClaudeCodeUsage/ClaudeCodeUsage) (last
upstream release: 1.0.8). Format follows [Keep a Changelog](https://keepachangelog.com).

## [Unreleased]
## [2.1.1] — Unreleased

### Fixed
- **Invalid timezone no longer breaks the dashboard** — a hand-typed bad IANA
zone (e.g. `EST-5 Detroit`) made `Intl` throw "Invalid time zone specified"
and the whole dashboard showed *Error*. The timezone setting is now validated;
an invalid value falls back to the system zone. (#51)
- **Sonnet 5 context window** — `contextWindowFor()` only recognised the 1M
window via a "4.6+" pattern (e.g. `sonnet-4-6`), so `claude-sonnet-5` — which
has no `-4-` segment — fell through to the 200K legacy default. The dashboard
and status-bar context bar now correctly show a 1M window for Sonnet 5.

## [2.1.1] — Unreleased
and status-bar context bar now correctly show a 1M window for Sonnet 5. (#50,
@UfukTanriverdi8)
- **Sonnet 5 pricing** — added an explicit `claude-sonnet-5` entry at the Sonnet
$3 / $15 tier (it already resolved via family inference; now explicit).

### Added
- **Monthly cost in the status bar** — the `statusBarMetric` setting gains a
Expand Down
19 changes: 17 additions & 2 deletions src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1891,15 +1891,30 @@ export class I18n {
return this.currentLanguage;
}

/** IANA timezone (e.g. "Asia/Hong_Kong"), or '' to use the system zone. */
/** IANA timezone (e.g. "Asia/Hong_Kong"), or '' to use the system zone.
* An invalid value (e.g. a hand-typed "EST-5 Detroit") is rejected and falls
* back to the system zone: `Intl.DateTimeFormat` throws on a bad `timeZone`,
* which otherwise crashed the whole dashboard with "Invalid time zone
* specified" (#51). */
static setTimezone(tz: string): void {
this.timezone = typeof tz === 'string' ? tz.trim() : '';
const clean = typeof tz === 'string' ? tz.trim() : '';
this.timezone = clean && I18n.isValidTimeZone(clean) ? clean : '';
}

static getTimezone(): string {
return this.timezone;
}

/** True if `tz` is an IANA zone Intl accepts (so date formatting won't throw). */
static isValidTimeZone(tz: string): boolean {
try {
new Intl.DateTimeFormat('en', { timeZone: tz });
return true;
} catch {
return false;
}
}

/** Intl date-format options merged with the configured timezone (if any). */
static dateFormatOptions(extra: Intl.DateTimeFormatOptions = {}): Intl.DateTimeFormatOptions {
return this.timezone ? { ...extra, timeZone: this.timezone } : extra;
Expand Down
4 changes: 4 additions & 0 deletions src/pricing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ const MODEL_PRICING: Record<string, ModelPricing> = {
// Claude Opus 4 (2025-05-14)
'claude-opus-4-20250514': OPUS_LEGACY,

// Claude Sonnet 5 (same $3 / $15 Sonnet tier). Family inference already maps
// any "sonnet" model to SONNET, so this is an explicit anchor for clarity.
'claude-sonnet-5': SONNET,

// Claude Sonnet 4.6
'claude-sonnet-4-6': SONNET,

Expand Down
31 changes: 31 additions & 0 deletions src/test/i18nTimezone.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { test } from 'node:test';
import * as assert from 'node:assert/strict';
import { I18n } from '../i18n';

test('a valid IANA zone is accepted', () => {
I18n.setTimezone('Asia/Hong_Kong');
assert.equal(I18n.getTimezone(), 'Asia/Hong_Kong');
});

test('an invalid zone falls back to system (empty), never crashes (#51)', () => {
I18n.setTimezone('EST-5 Detroit'); // what the bug reporter typed
assert.equal(I18n.getTimezone(), '');
// dateFormatOptions must not carry the bad value into Intl.
assert.doesNotThrow(() => new Intl.DateTimeFormat('en', I18n.dateFormatOptions()));
});

test('empty stays empty (system zone)', () => {
I18n.setTimezone('');
assert.equal(I18n.getTimezone(), '');
});

test('whitespace-padded invalid value is trimmed and rejected', () => {
I18n.setTimezone(' Not/AZone ');
assert.equal(I18n.getTimezone(), '');
});

test('isValidTimeZone distinguishes real zones from junk', () => {
assert.equal(I18n.isValidTimeZone('America/New_York'), true);
assert.equal(I18n.isValidTimeZone('UTC'), true);
assert.equal(I18n.isValidTimeZone('EST-5 Detroit'), false);
});
Loading