Skip to content
Merged
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 0.45.2.2 — 2026-07-24

### Fixed
- Alibaba Token Plan: restore the authenticated 5-hour and weekly rate windows, keep the legacy monthly credit response as a fallback, and label each restored window by its actual duration. Thanks @rohitsabu!

## 0.45.2.1 — 2026-07-19

### Added
Expand Down
17 changes: 17 additions & 0 deletions CodexBarMobile/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

All notable changes to the CodexBar iOS companion app will be documented in this file.

## [1.19.0 (189)] — 2026-07-24 — Alibaba Token Plan hotfix

### Fixed

- **Alibaba Token Plan limits** — Restored the rolling 5-hour and weekly
limits from the Mac companion and labeled both windows correctly while
retaining the existing monthly-credit fallback.

### Notes

- Pairs with Mac CodexBar `0.45.2.2` / build `109.2`; existing Shared payloads
and the CloudKit Production schema are unchanged.
- iOS `MARKETING_VERSION` remains `1.19.0`.
- iOS `CURRENT_PROJECT_VERSION`: `188` → `189`.

---

## [1.19.0 (188)] — 2026-07-19 — CodexBar 0.45 upstream sync

### Added
Expand Down
16 changes: 8 additions & 8 deletions CodexBarMobile/CodexBarMobile.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1246,7 +1246,7 @@
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_ENTITLEMENTS = CodexBarMobileWidgets/WidgetExtension.entitlements;
CURRENT_PROJECT_VERSION = 188;
CURRENT_PROJECT_VERSION = 189;
DEVELOPMENT_TEAM = 3TUERHN53E;
INFOPLIST_FILE = CodexBarMobileWidgets/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand All @@ -1266,7 +1266,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CURRENT_PROJECT_VERSION = 188;
CURRENT_PROJECT_VERSION = 189;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
Expand All @@ -1291,7 +1291,7 @@
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_ENTITLEMENTS = CodexBarMobileWidgets/WidgetExtension.entitlements;
CURRENT_PROJECT_VERSION = 188;
CURRENT_PROJECT_VERSION = 189;
DEVELOPMENT_TEAM = 3TUERHN53E;
INFOPLIST_FILE = CodexBarMobileWidgets/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down Expand Up @@ -1466,7 +1466,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = CodexBarMobilePushExtension/PushExtension.entitlements;
CURRENT_PROJECT_VERSION = 188;
CURRENT_PROJECT_VERSION = 189;
DEVELOPMENT_TEAM = 3TUERHN53E;
INFOPLIST_FILE = CodexBarMobilePushExtension/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand All @@ -1488,7 +1488,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = CodexBarMobile/CodexBarMobile.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CURRENT_PROJECT_VERSION = 188;
CURRENT_PROJECT_VERSION = 189;
DEVELOPMENT_TEAM = 3TUERHN53E;
INFOPLIST_FILE = CodexBarMobile/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down Expand Up @@ -1526,7 +1526,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = CodexBarMobile/CodexBarMobile.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CURRENT_PROJECT_VERSION = 188;
CURRENT_PROJECT_VERSION = 189;
DEVELOPMENT_TEAM = 3TUERHN53E;
INFOPLIST_FILE = CodexBarMobile/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down Expand Up @@ -1562,7 +1562,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CURRENT_PROJECT_VERSION = 188;
CURRENT_PROJECT_VERSION = 189;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
Expand All @@ -1586,7 +1586,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = CodexBarMobilePushExtension/PushExtension.entitlements;
CURRENT_PROJECT_VERSION = 188;
CURRENT_PROJECT_VERSION = 189;
DEVELOPMENT_TEAM = 3TUERHN53E;
INFOPLIST_FILE = CodexBarMobilePushExtension/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down
4 changes: 2 additions & 2 deletions CodexBarMobile/CodexBarMobile/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3665,15 +3665,15 @@ private enum MobileReleaseNotesCatalog {
String(
localized: "More complete details — sub2api shows account balance and request totals, while Wayfinder shows routing activity and savings."),
String(
localized: "Richer limit coverage — monthly and additional limits now stay visible alongside daily and weekly windows when a provider offers them."),
localized: "Richer limit coverage — Alibaba Token Plan restores its 5-hour and weekly limits, and monthly or additional limits stay visible when providers offer them."),
String(
localized: "A more capable Mac companion — customize menu bar layouts, see usage forecasts, use safer refresh controls, and benefit from the latest provider, performance, and security fixes."),
]),
.init(
title: String(localized: "Required Mac version"),
items: [
String(
localized: "For all new details, update CodexBar on Mac to version 0.45.2.1 or later. iPhone 1.19 still works with data from older Mac versions."),
localized: "For all new details, update CodexBar on Mac to version 0.45.2.2 or later. iPhone 1.19 still works with data from older Mac versions."),
]),
]),
ReleaseNotesVersion(
Expand Down
49 changes: 39 additions & 10 deletions CodexBarMobile/CodexBarMobile/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -13551,6 +13551,35 @@
}
}
},
"5-hour": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "5-hour"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "5時間"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "5 小时"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "5 小時"
}
}
}
},
"Alibaba Token Plan (Bailian) — monthly token-plan quota card showing used and total credits with the reset date, imported from browser or manual cookies.": {
"localizations": {
"en": {
Expand Down Expand Up @@ -19630,23 +19659,23 @@
"zh-Hans": { "stringUnit": { "state": "translated", "value": "更完整的详情——sub2api 显示账户余额和请求总计,Wayfinder 显示路由活动与节省金额。" } },
"zh-Hant": { "stringUnit": { "state": "translated", "value": "更完整的詳情——sub2api 顯示帳戶餘額和請求總計,Wayfinder 顯示路由活動與節省金額。" } }
} },
"Richer limit coverage — monthly and additional limits now stay visible alongside daily and weekly windows when a provider offers them.": { "extractionState": "manual", "localizations": {
"en": { "stringUnit": { "state": "translated", "value": "Richer limit coverage — monthly and additional limits now stay visible alongside daily and weekly windows when a provider offers them." } },
"ja": { "stringUnit": { "state": "translated", "value": "上限表示を拡充 — プロバイダーが提供する月間および追加の上限も、日次・週次の上限と並んで表示されます。" } },
"zh-Hans": { "stringUnit": { "state": "translated", "value": "更完整的额度展示——提供商支持时,月度和其他额度现在会与每日、每周额度一同保留显示。" } },
"zh-Hant": { "stringUnit": { "state": "translated", "value": "更完整的額度顯示——提供者支援時,月度和其他額度現在會與每日、每週額度一同保留顯示。" } }
"Richer limit coverage — Alibaba Token Plan restores its 5-hour and weekly limits, and monthly or additional limits stay visible when providers offer them.": { "extractionState": "manual", "localizations": {
"en": { "stringUnit": { "state": "translated", "value": "Richer limit coverage — Alibaba Token Plan restores its 5-hour and weekly limits, and monthly or additional limits stay visible when providers offer them." } },
"ja": { "stringUnit": { "state": "translated", "value": "上限表示を拡充 — Alibaba Token Plan の5時間・週間上限を復元し、プロバイダーが提供する月間および追加の上限も表示します。" } },
"zh-Hans": { "stringUnit": { "state": "translated", "value": "更完整的额度展示——恢复 Alibaba Token Plan 的 5 小时和每周额度,并继续显示提供商支持的月度及其他额度。" } },
"zh-Hant": { "stringUnit": { "state": "translated", "value": "更完整的額度顯示——恢復 Alibaba Token Plan 的 5 小時和每週額度,並繼續顯示提供者支援的月度及其他額度。" } }
} },
"A more capable Mac companion — customize menu bar layouts, see usage forecasts, use safer refresh controls, and benefit from the latest provider, performance, and security fixes.": { "extractionState": "manual", "localizations": {
"en": { "stringUnit": { "state": "translated", "value": "A more capable Mac companion — customize menu bar layouts, see usage forecasts, use safer refresh controls, and benefit from the latest provider, performance, and security fixes." } },
"ja": { "stringUnit": { "state": "translated", "value": "より高機能な Mac 版 — メニューバーのレイアウト編集、使用量予測、安全な更新操作に加え、プロバイダー、性能、セキュリティの最新修正を利用できます。" } },
"zh-Hans": { "stringUnit": { "state": "translated", "value": "功能更强的 Mac 端——可自定义菜单栏布局、查看用量预测、使用更安全的刷新控制,并获得最新的提供商、性能与安全修复。" } },
"zh-Hant": { "stringUnit": { "state": "translated", "value": "功能更強的 Mac 端——可自訂選單列佈局、查看用量預測、使用更安全的重新整理控制,並獲得最新的提供者、效能與安全修正。" } }
} },
"For all new details, update CodexBar on Mac to version 0.45.2.1 or later. iPhone 1.19 still works with data from older Mac versions.": { "extractionState": "manual", "localizations": {
"en": { "stringUnit": { "state": "translated", "value": "For all new details, update CodexBar on Mac to version 0.45.2.1 or later. iPhone 1.19 still works with data from older Mac versions." } },
"ja": { "stringUnit": { "state": "translated", "value": "すべての新しい詳細を表示するには、Mac の CodexBar を 0.45.2.1 以降に更新してください。iPhone 1.19 は古い Mac 版のデータも引き続き表示できます。" } },
"zh-Hans": { "stringUnit": { "state": "translated", "value": "要查看全部新详情,请将 Mac 上的 CodexBar 更新到 0.45.2.1 或更高版本。iPhone 1.19 仍可继续读取旧版 Mac 的数据。" } },
"zh-Hant": { "stringUnit": { "state": "translated", "value": "要查看全部新詳情,請將 Mac 上的 CodexBar 更新到 0.45.2.1 或更高版本。iPhone 1.19 仍可繼續讀取舊版 Mac 的資料。" } }
"For all new details, update CodexBar on Mac to version 0.45.2.2 or later. iPhone 1.19 still works with data from older Mac versions.": { "extractionState": "manual", "localizations": {
"en": { "stringUnit": { "state": "translated", "value": "For all new details, update CodexBar on Mac to version 0.45.2.2 or later. iPhone 1.19 still works with data from older Mac versions." } },
"ja": { "stringUnit": { "state": "translated", "value": "すべての新しい詳細を表示するには、Mac の CodexBar を 0.45.2.2 以降に更新してください。iPhone 1.19 は古い Mac 版のデータも引き続き表示できます。" } },
"zh-Hans": { "stringUnit": { "state": "translated", "value": "要查看全部新详情,请将 Mac 上的 CodexBar 更新到 0.45.2.2 或更高版本。iPhone 1.19 仍可继续读取旧版 Mac 的数据。" } },
"zh-Hant": { "stringUnit": { "state": "translated", "value": "要查看全部新詳情,請將 Mac 上的 CodexBar 更新到 0.45.2.2 或更高版本。iPhone 1.19 仍可繼續讀取舊版 Mac 的資料。" } }
} }
},
"version": "1.0"
Expand Down
6 changes: 6 additions & 0 deletions CodexBarMobile/CodexBarMobile/Views/V045ProviderCards.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import SwiftUI
enum ProviderWindowLabel {
static func localizationKey(for label: String?) -> String? {
switch label {
case "5-hour": "5-hour"
case "Credits": "Credits"
case "Daily": "v045_window_daily"
case "Weekly": "v045_window_weekly"
case "Monthly": "v045_window_monthly"
Expand Down Expand Up @@ -33,6 +35,10 @@ enum ProviderWindowLabel {
return label ?? fallback
}
switch key {
case "5-hour":
return String(localized: "5-hour")
case "Credits":
return String(localized: "Credits", defaultValue: "Credits")
case "v045_window_daily":
return String(localized: "v045_window_daily", defaultValue: "Daily")
case "v045_window_weekly":
Expand Down
57 changes: 57 additions & 0 deletions CodexBarMobile/CodexBarMobileTests/CloudKitMergeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,63 @@ struct CloudKitMergeTests {
#expect(kimi.rateWindows.last?.usedPercent == 10)
}

@Test(arguments: [false, true])
func `Mixed Alibaba writers preserve rolling lanes in both freshness orders`(
oldMacIsFresher: Bool) throws
{
let oldDate = oldMacIsFresher ? self.newerDate : self.olderDate
let newDate = oldMacIsFresher ? self.olderDate : self.newerDate
let oldMacProvider = ProviderUsageSnapshot(
providerID: "alibabatokenplan", providerName: "Alibaba Token Plan",
primary: SyncRateWindow(
label: "Credits", usedPercent: 30, windowMinutes: 43200,
resetsAt: nil, resetDescription: nil),
secondary: nil,
accountEmail: nil, loginMethod: "Bailian Pro",
statusMessage: nil, isError: false, lastUpdated: oldDate,
rateWindows: [
SyncRateWindow(
label: "Credits", usedPercent: 30, windowMinutes: 43200,
resetsAt: nil, resetDescription: nil),
])
let newMacProvider = ProviderUsageSnapshot(
providerID: "alibabatokenplan", providerName: "Alibaba Token Plan",
primary: SyncRateWindow(
label: "5-hour", usedPercent: 10, windowMinutes: 300,
resetsAt: nil, resetDescription: nil),
secondary: SyncRateWindow(
label: "Weekly", usedPercent: 20, windowMinutes: 10080,
resetsAt: nil, resetDescription: nil),
accountEmail: nil, loginMethod: "Bailian Pro",
statusMessage: nil, isError: false, lastUpdated: newDate,
rateWindows: [
SyncRateWindow(
label: "5-hour", usedPercent: 10, windowMinutes: 300,
resetsAt: nil, resetDescription: nil),
SyncRateWindow(
label: "Weekly", usedPercent: 20, windowMinutes: 10080,
resetsAt: nil, resetDescription: nil),
SyncRateWindow(
label: "Credits", usedPercent: 30, windowMinutes: 43200,
resetsAt: nil, resetDescription: nil),
])

let oldMac = self.makeSnapshot(
deviceName: "Old Mac", deviceID: "uuid-old", providers: [oldMacProvider],
appVersion: "0.45.2.1")
let newMac = self.makeSnapshot(
deviceName: "New Mac", deviceID: "uuid-new", providers: [newMacProvider],
appVersion: "0.45.2.2")
let iPhoneA = try #require(CloudSyncReader.mergeSnapshots([oldMac, newMac]))
let iPhoneB = try #require(CloudSyncReader.mergeSnapshots([newMac, oldMac]))
let alibabaA = try #require(iPhoneA.providers.first)
let alibabaB = try #require(iPhoneB.providers.first)

#expect(alibabaA.rateWindows.compactMap(\.label) == ["5-hour", "Weekly", "Credits"])
#expect(alibabaA.rateWindows.map(\.windowMinutes) == [300, 10080, 43200])
#expect(alibabaB.rateWindows == alibabaA.rateWindows)
}

@Test(arguments: [false, true])
func `Mixed Claude writers preserve a specific Max tier in both freshness orders`(
oldMacIsFresher: Bool) throws
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ struct V045ProviderPresentationTests {
func `canonical generic window labels map to localized semantic keys`() {
#expect(ProviderWindowLabel.localizationKey(for: "Daily") == "v045_window_daily")
#expect(ProviderWindowLabel.localizationKey(for: "Weekly") == "v045_window_weekly")
#expect(ProviderWindowLabel.localizationKey(for: "5-hour") == "5-hour")
#expect(ProviderWindowLabel.localizationKey(for: "Credits") == "Credits")
#expect(ProviderWindowLabel.localizationKey(for: "Monthly") == "v045_window_monthly")
#expect(ProviderWindowLabel.localizationKey(for: "Additional") == "v045_window_additional")
#expect(ProviderWindowLabel.localizationKey(for: "5 hour limit") == "v045_window_5_hour_limit")
Expand Down
39 changes: 27 additions & 12 deletions CodexBarMobile/CodexBarWidgetShared/ProviderSnapshotMerger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -251,15 +251,19 @@ enum ProviderSnapshotMerger {
.first(where: specificMaxLabels.contains) ?? latest
}

/// Kimi and Claude added named lanes over several Mac releases. Preserve a
/// lane supplied by any active writer while taking overlapping values from
/// the freshest writer. Kimi then restores its canonical mobile order;
/// Claude keeps freshest-writer order followed by missing lanes.
/// Kimi, Claude, and Alibaba Token Plan added named lanes over several Mac
/// releases. Preserve a lane supplied by any active writer while taking
/// overlapping values from the freshest writer. Providers with fixed lane
/// semantics then restore their canonical mobile order; Claude keeps
/// freshest-writer order followed by missing lanes.
private static func mergedRateWindows(
_ entries: [ProviderUsageSnapshot],
base: ProviderUsageSnapshot) -> [SyncRateWindow]
{
guard base.providerID == "kimi" || base.providerID == "claude" else {
guard base.providerID == "kimi"
|| base.providerID == "claude"
|| base.providerID == "alibabatokenplan"
else {
return base.rateWindows
}

Expand All @@ -276,13 +280,24 @@ enum ProviderSnapshotMerger {
}
}

guard base.providerID == "kimi" else { return merged }
let preferredOrder = [
"weekly": 0,
"rate limit": 1,
"monthly": 2,
"code 7-day": 3,
]
let preferredOrder: [String: Int]
switch base.providerID {
case "kimi":
preferredOrder = [
"weekly": 0,
"rate limit": 1,
"monthly": 2,
"code 7-day": 3,
]
case "alibabatokenplan":
preferredOrder = [
"5-hour": 0,
"weekly": 1,
"credits": 2,
]
default:
return merged
}
return merged.enumerated().sorted { lhs, rhs in
let lhsRank = Self.normalizedRateWindowLabel(lhs.element)
.flatMap { preferredOrder[$0] } ?? Int.max
Expand Down
Loading