Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d888793
fix: prevent repeated rare candy grants from volatile reset timestamp…
KillianHzr Oct 15, 2026
a4d17d5
fix(collection): return to the viewed Pokédex page and catch log row …
heli-os Oct 16, 2026
04b12da
feat: collect alternate forms of generation 1-5 species (#376)
DyRize Oct 16, 2026
fd12f00
fix(settings): collapse automatic backup history by default (#446)
chattymin Oct 16, 2026
dc6de41
fix: correct pricing, backup appearances, and token recovery
chattymin Oct 16, 2026
594be29
docs: describe changes since version 2.5.5
chattymin Oct 16, 2026
17cf2bf
test: render form metadata captions at a fixed pixel density
chattymin Oct 16, 2026
2c0212b
docs: label settings images as native sample renders
chattymin Oct 16, 2026
4617c8a
docs: refresh release images from native sample views
chattymin Oct 16, 2026
b716054
fix: reveal session key settings without waiting for animation
chattymin Oct 16, 2026
77f789d
release: bump version to 2.5.6
chattymin Oct 16, 2026
2f2fb32
fix(sprites): recover animations after failed downloads (#447)
chattymin Oct 16, 2026
d82867b
fix(app): prevent empty Settings windows on launch and reopen (#448)
chattymin Oct 19, 2026
07a96fa
fix(cursor): share refreshed credentials across usage requests (#455)
chattymin Oct 24, 2026
8f74054
fix: parse Claude session logs incrementally instead of re-reading wh…
notcadenwhitt-stack Oct 24, 2026
c1c92c1
fix(home): label the usage recap control (#432)
Stephan1226 Oct 24, 2026
32ebe4c
test(ci): stabilize catch-log OCR fixture (#456)
chattymin Oct 24, 2026
eddd372
chore: sync fork with upstream (v2.5.6, #338–#456)
Oct 24, 2026
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
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,20 @@ jobs:
# line-coverage floor on the logic-core files. It replaces a bare `swift test`:
# the tests still run, and a coverage regression now fails the build.
- name: Test + coverage gate
env:
PTB_SNAPSHOT_SCREENSHOT_DIR: ${{ runner.temp }}/snapshot-history-renders
PTB_ZERO_USAGE_SCREENSHOT_DIR: ${{ runner.temp }}/snapshot-history-renders/zero-usage
run: ./scripts/test-gate.sh

- name: Preserve native snapshot-history renders
if: always()
uses: actions/upload-artifact@v4
with:
name: snapshot-history-renders
path: ${{ runner.temp }}/snapshot-history-renders
if-no-files-found: ignore
retention-days: 7

secret-scan:
name: secret-scan
runs-on: ubuntu-latest
Expand Down
54 changes: 37 additions & 17 deletions PokeTokenBarShared/Sources/PokeTokenBarShared/UsageEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ public enum ModelPricing {
// Opus 5.5, checked 2026-09-25: cheaper than Opus 5, cache read cut to $0.20/MTok (0.05× input).
"claude-opus-5-5": .perMillion(4, 20, 5, 0.2),
"claude-sonnet-5": .perMillion(2, 10, 2.5, 0.2),
// Sonnet 5.5, checked 2026-09-29: 5m cache write $2.50 and cache read $0.20 / MTok.
"claude-sonnet-5-5": .perMillion(2, 10, 2.5, 0.2),
// Sonnet 5.5, checked 2026-10-16: 5m cache write $2.50 and cache read $0.10 / MTok.
"claude-sonnet-5-5": .perMillion(2, 10, 2.5, 0.1),
"claude-opus-4-20250514": .perMillion(15, 75, 18.75, 1.5),
"claude-sonnet-4-20250514": .perMillion(3, 15, 3.75, 0.3),
"claude-sonnet-4-5-20250929": .perMillion(3, 15, 3.75, 0.3),
Expand Down Expand Up @@ -739,23 +739,43 @@ extension ProviderEnrichment {

public enum ISO8601Parser {
/// resets_at 은 마이크로초("...034464+00:00") 또는 밀리초("....303Z") 형태 — 둘 다 처리.
/// ISO8601DateFormatter 는 non-Sendable 이라 호출마다 생성 (파싱 빈도 낮음).
public static func date(from string: String) -> Date? {
let fractional = ISO8601DateFormatter()
fractional.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
if let d = fractional.date(from: string) { return d }
// 소수점 자릿수가 3자리가 아니면 3자리로 절단 후 재시도
if let dotIndex = string.firstIndex(of: ".") {
let afterDot = string.index(after: dotIndex)
if let tzIndex = string[afterDot...].firstIndex(where: { $0 == "+" || $0 == "-" || $0 == "Z" }) {
let frac = String(string[afterDot..<tzIndex]).prefix(3)
let padded = String(frac).padding(toLength: 3, withPad: "0", startingAt: 0)
let rebuilt = String(string[..<dotIndex]) + "." + padded + String(string[tzIndex...])
if let d = fractional.date(from: rebuilt) { return d }
shared.date(from: string)
}

private static let shared = LockedParser()

/// This is also the per-line timestamp parser for every local session log, so it runs
/// millions of times on a large history. Allocating an `ISO8601DateFormatter` per call was
/// the dominant cost of a cold scan. The formatters are non-Sendable and callers (the
/// provider caches) run concurrently, so one shared pair is kept behind a lock.
private final class LockedParser: @unchecked Sendable {
private let lock = NSLock()
private let fractional: ISO8601DateFormatter
private let plain: ISO8601DateFormatter

init() {
fractional = ISO8601DateFormatter()
fractional.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
plain = ISO8601DateFormatter()
plain.formatOptions = [.withInternetDateTime]
}

func date(from string: String) -> Date? {
lock.lock()
defer { lock.unlock() }
if let d = fractional.date(from: string) { return d }
// 소수점 자릿수가 3자리가 아니면 3자리로 절단 후 재시도
if let dotIndex = string.firstIndex(of: ".") {
let afterDot = string.index(after: dotIndex)
if let tzIndex = string[afterDot...].firstIndex(where: { $0 == "+" || $0 == "-" || $0 == "Z" }) {
let frac = String(string[afterDot..<tzIndex]).prefix(3)
let padded = String(frac).padding(toLength: 3, withPad: "0", startingAt: 0)
let rebuilt = String(string[..<dotIndex]) + "." + padded + String(string[tzIndex...])
if let d = fractional.date(from: rebuilt) { return d }
}
}
return plain.date(from: string)
}
let plain = ISO8601DateFormatter()
plain.formatOptions = [.withInternetDateTime]
return plain.date(from: string)
}
}
Loading
Loading