From 631296b554a2a3e8cc91c61e8a290ca9b5a10991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AI=E4=BA=A7=E5=93=81=E9=BB=84=E5=8F=94?= Date: Sun, 27 Sep 2026 05:15:34 -0700 Subject: [PATCH 1/3] fix(popover): keep time-based text current and stale quotas out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Island: the mouse-inside check still inset the top by the shadow margin the card window no longer has, so after the "..." menu closed with the mouse on the card's top 26pt, the card collapsed under it. - A quota window past its reset shows "已重置" and a dash instead of the old percentage, and the app checks quota again 30 seconds after the soonest reset rather than waiting out the regular interval. - The popover redraws every minute, so reset countdowns, "N minutes ago", pace markers and the run-out warning move while it stays open. - Link cursors set the pointing hand instead of pushing it; a popover that closes under the mouse never sends the hover end, which left the cursor stack unbalanced. Co-Authored-By: Claude Opus 5.5 --- .../TokenStepSwift/Stores/AppState.swift | 23 ++++++++ .../TokenStepSwift/Support/Localization.swift | 4 ++ .../Support/TokenIslandWindowPresenter.swift | 10 +++- .../Views/Popover/CompactPopoverModel.swift | 15 +++++ .../Popover/CompactPopoverSections.swift | 35 +++++++++--- .../Views/PopoverPanelView.swift | 55 +++++++++++-------- .../Fixtures/CompactPopoverFixtureCheck.swift | 3 + 7 files changed, 112 insertions(+), 33 deletions(-) diff --git a/TokenStepSwift/Sources/TokenStepSwift/Stores/AppState.swift b/TokenStepSwift/Sources/TokenStepSwift/Stores/AppState.swift index d16c520..0b1bbd6 100644 --- a/TokenStepSwift/Sources/TokenStepSwift/Stores/AppState.swift +++ b/TokenStepSwift/Sources/TokenStepSwift/Stores/AppState.swift @@ -38,6 +38,7 @@ final class AppState: ObservableObject { private var pendingRefreshAfterCurrent = false private var pendingForcedRefresh = false private var lastQuotaRefreshAttemptAt: Date? + private var quotaResetTimer: Timer? private var lastCursorUsageRefreshAttemptAt: Date? private var lastRankRefreshAttemptAt: Date? private var lastAutomaticUsageRefreshAttemptAt: Date? @@ -325,6 +326,28 @@ final class AppState: ObservableObject { } quotas = quotas.filter { providers.contains($0.key) } isRefreshingCodexQuota = false + scheduleQuotaResetRefresh() + } + } + + /// Checks quota again right after the soonest window resets, so a reset + /// window does not wait out the regular interval showing no reading. + private func scheduleQuotaResetRefresh(now: Date = Date()) { + quotaResetTimer?.invalidate() + quotaResetTimer = nil + let nextReset = quotas.values + .filter(\.isAvailable) + .flatMap(\.windows) + .compactMap(\.resetsAt) + .filter { $0 > now } + .min() + guard let nextReset else { return } + // A short grace period lets the provider roll the window over first. + let delay = nextReset.timeIntervalSince(now) + 30 + quotaResetTimer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { [weak self] _ in + Task { @MainActor in + self?.refreshCodexQuota(force: true, bypassCache: true) + } } } diff --git a/TokenStepSwift/Sources/TokenStepSwift/Support/Localization.swift b/TokenStepSwift/Sources/TokenStepSwift/Support/Localization.swift index 6060dbf..1cca0da 100644 --- a/TokenStepSwift/Sources/TokenStepSwift/Support/Localization.swift +++ b/TokenStepSwift/Sources/TokenStepSwift/Support/Localization.swift @@ -513,6 +513,8 @@ enum TokenStepLocalization { "榜单 %@": "Board %@", "照此速度会提前用完": "Runs out early at this pace", "%@额度会提前用完": "%@ runs out early", + "已重置": "Reset", + "已过重置时间,等待下一次读取额度": "Past its reset time; waiting for the next quota check", "打开我的 Token Rank 主页": "Open my Token Rank page", "在浏览器打开 Token Rank 榜单": "Open the Token Rank board in the browser", "已用 %@": "%@ used", @@ -1208,6 +1210,8 @@ enum TokenStepLocalization { "榜单 %@": "榜單 %@", "照此速度会提前用完": "照此速度會提前用完", "%@额度会提前用完": "%@額度會提前用完", + "已重置": "已重置", + "已过重置时间,等待下一次读取额度": "已過重置時間,等待下一次讀取額度", "打开我的 Token Rank 主页": "打開我的 Token Rank 主頁", "在浏览器打开 Token Rank 榜单": "在瀏覽器打開 Token Rank 榜單", "已用 %@": "已用 %@", diff --git a/TokenStepSwift/Sources/TokenStepSwift/Support/TokenIslandWindowPresenter.swift b/TokenStepSwift/Sources/TokenStepSwift/Support/TokenIslandWindowPresenter.swift index 0cef5f1..b547655 100644 --- a/TokenStepSwift/Sources/TokenStepSwift/Support/TokenIslandWindowPresenter.swift +++ b/TokenStepSwift/Sources/TokenStepSwift/Support/TokenIslandWindowPresenter.swift @@ -75,7 +75,15 @@ final class TokenIslandWindowPresenter { private var isMouseInsidePopover: Bool { guard let panel = popoverPanel else { return false } - let card = panel.frame.insetBy(dx: TokenIslandMetrics.expandedShadowMargin, dy: TokenIslandMetrics.expandedShadowMargin) + // The window has shadow margin on the sides and bottom only. + let margin = TokenIslandMetrics.expandedShadowMargin + let frame = panel.frame + let card = NSRect( + x: frame.minX + margin, + y: frame.minY + margin, + width: frame.width - margin * 2, + height: frame.height - margin + ) let mouse = NSEvent.mouseLocation return card.contains(mouse) || (ringPanel?.frame.contains(mouse) ?? false) } diff --git a/TokenStepSwift/Sources/TokenStepSwift/Views/Popover/CompactPopoverModel.swift b/TokenStepSwift/Sources/TokenStepSwift/Views/Popover/CompactPopoverModel.swift index 28a50d9..d7cf807 100644 --- a/TokenStepSwift/Sources/TokenStepSwift/Views/Popover/CompactPopoverModel.swift +++ b/TokenStepSwift/Sources/TokenStepSwift/Views/Popover/CompactPopoverModel.swift @@ -37,6 +37,9 @@ enum CompactPopoverModel { var elapsedFraction: Double? /// At the current pace the window runs out before it resets. var runsOutEarly: Bool + /// The reset time has passed since the reading, so its percentage is + /// stale until the next quota check. + var isReset = false } struct QuotaProviderRow: Equatable, Identifiable { @@ -116,6 +119,18 @@ enum CompactPopoverModel { } static func windowRow(_ window: QuotaWindow, now: Date, calendar: Calendar) -> QuotaWindowRow { + if let resetsAt = window.resetsAt, resetsAt <= now { + return QuotaWindowRow( + kind: window.kind, + title: window.title, + usedPercent: 0, + level: .normal, + resetsAt: resetsAt, + elapsedFraction: nil, + runsOutEarly: false, + isReset: true + ) + } let used = min(max(100 - window.remainingPercent, 0), 100) let elapsed = elapsedFraction(kind: window.kind, resetsAt: window.resetsAt, now: now, calendar: calendar) let runsOutEarly: Bool diff --git a/TokenStepSwift/Sources/TokenStepSwift/Views/Popover/CompactPopoverSections.swift b/TokenStepSwift/Sources/TokenStepSwift/Views/Popover/CompactPopoverSections.swift index 9bf2d1a..8561d54 100644 --- a/TokenStepSwift/Sources/TokenStepSwift/Views/Popover/CompactPopoverSections.swift +++ b/TokenStepSwift/Sources/TokenStepSwift/Views/Popover/CompactPopoverSections.swift @@ -94,6 +94,25 @@ struct QuotaProviderIcon: View { } } +/// Shows the pointing hand over a clickable area. It sets the cursor rather +/// than pushing it, so a popover that closes under the mouse (and never sends +/// the hover end) cannot leave an unbalanced cursor stack behind. +private struct LinkCursor: ViewModifier { + var isEnabled: Bool + + func body(content: Content) -> some View { + content.onContinuousHover { phase in + guard isEnabled else { return } + switch phase { + case .active: + NSCursor.pointingHand.set() + case .ended: + NSCursor.arrow.set() + } + } + } +} + // MARK: - Shared styling enum CompactPopoverStyle { @@ -300,6 +319,9 @@ struct CompactQuotaSection: View { /// Explains the bar's tick: how much of the window's time has passed. private func paceHelp(_ window: CompactPopoverModel.QuotaWindowRow) -> String { + if window.isReset { + return L("已过重置时间,等待下一次读取额度") + } guard let elapsed = window.elapsedFraction else { return LFormat("已用 %@", CompactPopoverStyle.percent(window.usedPercent)) } @@ -342,12 +364,12 @@ struct CompactQuotaSection: View { color: CompactPopoverStyle.color(for: window.level), marker: window.elapsedFraction ) - Text(CompactPopoverStyle.percent(window.usedPercent)) + Text(window.isReset ? "—" : CompactPopoverStyle.percent(window.usedPercent)) .font(.system(size: 12, weight: .bold)) .monospacedDigit() .foregroundStyle(CompactPopoverStyle.textColor(for: window.level)) .frame(width: 38, alignment: .trailing) - Text(CompactPopoverStyle.resetText(window.resetsAt) ?? "") + Text(window.isReset ? L("已重置") : CompactPopoverStyle.resetText(window.resetsAt) ?? "") .font(.system(size: 11)) .foregroundStyle(.secondary) .lineLimit(1) @@ -454,10 +476,7 @@ struct CompactRankSection: View { } .contentShape(Rectangle()) .onTapGesture { openMyPage?() } - .onHover { hovering in - guard openMyPage != nil else { return } - if hovering { NSCursor.pointingHand.push() } else { NSCursor.pop() } - } + .modifier(LinkCursor(isEnabled: openMyPage != nil)) .help(openMyPage == nil ? "" : L("打开我的 Token Rank 主页")) ForEach(section.podium) { entry in HStack(spacing: 8) { @@ -513,9 +532,7 @@ struct CompactRankSection: View { if let openBoard { Button(action: openBoard) { label.contentShape(Rectangle()) } .buttonStyle(.plain) - .onHover { hovering in - if hovering { NSCursor.pointingHand.push() } else { NSCursor.pop() } - } + .modifier(LinkCursor(isEnabled: true)) .help(L("在浏览器打开 Token Rank 榜单")) } else { label diff --git a/TokenStepSwift/Sources/TokenStepSwift/Views/PopoverPanelView.swift b/TokenStepSwift/Sources/TokenStepSwift/Views/PopoverPanelView.swift index fd9980c..2c8a183 100644 --- a/TokenStepSwift/Sources/TokenStepSwift/Views/PopoverPanelView.swift +++ b/TokenStepSwift/Sources/TokenStepSwift/Views/PopoverPanelView.swift @@ -9,30 +9,14 @@ struct PopoverPanelView: View { @State private var interstellarManualPulseTrigger = 0 var body: some View { - VStack(alignment: .leading, spacing: 0) { - let quotaSection = CompactPopoverModel.quotaSection(quotas: appState.visibleQuotas, now: Date()) - if !quotaSection.isEmpty { - section { - CompactQuotaSection(section: quotaSection, updatedAt: quotaUpdatedAt) - } - sectionDivider - } - section { - CompactTokenSection(usage: appState.today, lap: appState.todayLap) - } - if let rank = rankSection, let board = appState.tokenRank { - sectionDivider - section { - CompactRankSection( - section: rank, - fetchedAt: board.fetchedAt, - openBoard: { appState.openTokenRankLeaderboardPage() }, - openMyPage: { appState.openTokenRankUserPage() } - ) - } + // Reset countdowns, "N minutes ago", pace markers and the run-out + // warning depend on the clock, so redraw every minute while open. + TimelineView(.everyMinute) { context in + VStack(alignment: .leading, spacing: 0) { + content(now: context.date) + notices + footer } - notices - footer } .frame(width: CompactPopoverStyle.width) .background { @@ -78,6 +62,31 @@ struct PopoverPanelView: View { } } + @ViewBuilder + private func content(now: Date) -> some View { + let quotaSection = CompactPopoverModel.quotaSection(quotas: appState.visibleQuotas, now: now) + if !quotaSection.isEmpty { + section { + CompactQuotaSection(section: quotaSection, updatedAt: quotaUpdatedAt) + } + sectionDivider + } + section { + CompactTokenSection(usage: appState.today, lap: appState.todayLap) + } + if let rank = rankSection, let board = appState.tokenRank { + sectionDivider + section { + CompactRankSection( + section: rank, + fetchedAt: board.fetchedAt, + openBoard: { appState.openTokenRankLeaderboardPage() }, + openMyPage: { appState.openTokenRankUserPage() } + ) + } + } + } + /// Cinematic themes put each section on a translucent panel so text stays /// readable over the artwork; the classic theme draws sections directly. @ViewBuilder diff --git a/TokenStepSwift/Tests/Fixtures/CompactPopoverFixtureCheck.swift b/TokenStepSwift/Tests/Fixtures/CompactPopoverFixtureCheck.swift index 7d032e0..adbc9f7 100644 --- a/TokenStepSwift/Tests/Fixtures/CompactPopoverFixtureCheck.swift +++ b/TokenStepSwift/Tests/Fixtures/CompactPopoverFixtureCheck.swift @@ -97,6 +97,9 @@ struct CompactPopoverFixtureCheck { let cursor = row(.cursorModels, used: 50, resetsIn: hours(24)).elapsedFraction ?? 0 try expect(cursor > 0.9 && cursor < 1, "cursor model window follows the monthly billing cycle: \(cursor)") try expect(row(.fiveHour, used: 50, resetsIn: nil).elapsedFraction == nil, "no reset time has no marker") + let pastReset = row(.fiveHour, used: 71, resetsIn: -hours(1)) + try expect(pastReset.isReset && pastReset.usedPercent == 0 && !pastReset.runsOutEarly && pastReset.elapsedFraction == nil, + "a window past its reset drops the stale percentage") // Resets 2026-10-01 00:00 Shanghai; the window began 2026-09-01 00:00. let reset = ISO8601DateFormatter().date(from: "2026-09-30T16:00:00Z")! let monthly = CompactPopoverModel.windowRow( From c6b4cc7cb9ecbe1f995477a7d9877464dd703252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AI=E4=BA=A7=E5=93=81=E9=BB=84=E5=8F=94?= Date: Sun, 27 Sep 2026 05:16:17 -0700 Subject: [PATCH 2/3] chore(release): add prepare_release.sh for the release documents Each release edited the same five places by hand: the notes, the CHANGELOG entry, README's latest-version section and two DMG links, the packaging example, and the build script default. The script does all but the notes, refuses a version that is not newer, stops with a template when the notes are missing, and runs the release contract check. It does not commit. Co-Authored-By: Claude Opus 5.5 --- docs/RELEASE.md | 8 +++ script/prepare_release.sh | 124 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100755 script/prepare_release.sh diff --git a/docs/RELEASE.md b/docs/RELEASE.md index 5051da2..6a841ea 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -82,6 +82,14 @@ Before the release commit, update the release documents (the Release workflow ch - Add the version's summary at the top of `CHANGELOG.md`. - Update the "最新版本" section and the DMG download links in `README.md` to `TokenStep-.dmg`. +`script/prepare_release.sh` does all of this except writing the notes. The first run creates a notes template and stops. Fill it in, then run it again: + +```bash +./script/prepare_release.sh 0.2.20 "" "" +``` + +It also moves the default build version and runs the release contract check. It does not commit. + 1. Merge the release commit to `main` and wait for CI. 2. Run the repository's `Release` workflow from `main` with the exact version. 3. The workflow creates a draft and uploads the notarized DMG, ZIP, and checksum file. diff --git a/script/prepare_release.sh b/script/prepare_release.sh new file mode 100755 index 0000000..a1d25dc --- /dev/null +++ b/script/prepare_release.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Prepares the release documents for a new version, the edits +# docs/RELEASE.md lists under "Publish to GitHub": +# +# - CHANGELOG.md gets the version's entry at the top. +# - README.md's "最新版本" section, DMG links and example version move to it. +# - script/build_swiftui_and_run.sh defaults to it. +# +# The release notes are written by hand. When docs/RELEASE_NOTES_.md +# does not exist yet, this creates a template and stops so it can be filled in. +# Nothing is committed. +# +# Usage: +# script/prepare_release.sh "" "" +# +# Example: +# script/prepare_release.sh 0.2.20 "修复额度与刘海卡片细节" "额度窗口过了重置时间显示「已重置」……" + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +if [[ $# -ne 3 ]]; then + sed -n '15,19p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' >&2 + exit 2 +fi + +VERSION="$1" +TITLE="$2" +SUMMARY="$3" +NOTES="$ROOT_DIR/docs/RELEASE_NOTES_$VERSION.md" + +if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Version must look like 0.2.20, got: $VERSION" >&2 + exit 2 +fi + +if [[ ! -f "$NOTES" ]]; then + cat > "$NOTES" <&2 + exit 1 +fi + +python3 - "$ROOT_DIR" "$VERSION" "$TITLE" "$SUMMARY" <<'PY' +import re +import sys +from pathlib import Path + +root, version, title, summary = Path(sys.argv[1]), sys.argv[2], sys.argv[3], sys.argv[4] + + +def version_key(value): + return tuple(int(part) for part in value.split(".")) + + +def fail(message): + sys.exit(f"prepare_release: {message}") + + +notes_path = root / f"docs/RELEASE_NOTES_{version}.md" +if not notes_path.read_text().startswith(f"# TokenStep {version}\n"): + fail(f"{notes_path.name} must start with '# TokenStep {version}'") + +build_path = root / "script/build_swiftui_and_run.sh" +build = build_path.read_text() +match = re.search(r'VERSION="\$\{TOKENSTEP_VERSION:-([0-9.]+)\}"', build) +if not match: + fail("could not find the default VERSION in build_swiftui_and_run.sh") +current = match.group(1) +if version_key(version) <= version_key(current): + fail(f"{version} is not newer than the current {current}") + +link = f"详见 [{version} 发布说明](docs/RELEASE_NOTES_{version}.md)。" +paragraph = summary.rstrip() +if not paragraph.endswith(("。", ".", "!", "?")): + paragraph += "。" + +# CHANGELOG: new entry above the first version heading. +changelog_path = root / "CHANGELOG.md" +changelog = changelog_path.read_text() +if f"## {version} 更新" in changelog: + fail(f"CHANGELOG.md already has an entry for {version}") +first = re.search(r"^## \d+\.\d+\.\d+ ", changelog, re.M) +if not first: + fail("could not find a version heading in CHANGELOG.md") +entry = f"## {version} 更新:{title}\n\n{paragraph}{link}\n\n" +changelog = changelog[: first.start()] + entry + changelog[first.start():] + +# README: the latest-version section, DMG links and the packaging example. +readme_path = root / "README.md" +readme = readme_path.read_text() +readme, sections = re.subn( + rf"## 最新版本:{re.escape(current)}\n\n.*?\n\n", + f"## 最新版本:{version}\n\n{paragraph}{link}\n\n", + readme, + count=1, + flags=re.S, +) +if sections != 1: + fail(f"could not find '## 最新版本:{current}' in README.md") +dmg_links = readme.count(f"TokenStep-{current}.dmg") +if dmg_links == 0: + fail(f"README.md has no TokenStep-{current}.dmg links") +readme = readme.replace(f"TokenStep-{current}.dmg", f"TokenStep-{version}.dmg") +readme = readme.replace(f"TOKENSTEP_VERSION={current}", f"TOKENSTEP_VERSION={version}") + +build = build.replace(match.group(0), f'VERSION="${{TOKENSTEP_VERSION:-{version}}}"') + +changelog_path.write_text(changelog) +readme_path.write_text(readme) +build_path.write_text(build) +print(f"{current} -> {version}: CHANGELOG entry, README section and {dmg_links} DMG links, build default.") +PY + +"$ROOT_DIR/script/test_release_contract.sh" +git -C "$ROOT_DIR" status --short -- CHANGELOG.md README.md script/build_swiftui_and_run.sh "docs/RELEASE_NOTES_$VERSION.md" From 774ac50d59cc6d9df35d3e61119c479e6c4ded17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AI=E4=BA=A7=E5=93=81=E9=BB=84=E5=8F=94?= Date: Sun, 27 Sep 2026 05:17:18 -0700 Subject: [PATCH 3/3] chore: prepare TokenStep 0.2.20 Release notes, CHANGELOG, README and the build default, via script/prepare_release.sh. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 ++++ README.md | 10 +++++----- docs/RELEASE_NOTES_0.2.20.md | 10 ++++++++++ script/build_swiftui_and_run.sh | 2 +- 4 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 docs/RELEASE_NOTES_0.2.20.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 64230ef..80eed20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ TokenStep 各版本的更新内容,新版本在前。每个版本的完整说明在 [docs/RELEASE_NOTES_<版本号>.md](docs/)。 +## 0.2.20 更新:额度到点重置,面板信息保持最新 + +额度窗口过了重置时间显示「已重置」并自动重新读取;面板一直开着时,倒计时、时间进度竖线和提前用完的提示也会每分钟更新;修复刘海卡片关闭菜单后意外收起、鼠标指针停在手形的问题。详见 [0.2.20 发布说明](docs/RELEASE_NOTES_0.2.20.md)。 + ## 0.2.19 更新:额度与排名更精简 额度条粗细统一,Cursor 模型额度也有时间进度竖线;排名只在前 10 名时显示前三名,改为显示离你最近的后一名;大数字的单位更紧凑,页脚更简洁。详见 [0.2.19 发布说明](docs/RELEASE_NOTES_0.2.19.md)。 diff --git a/README.md b/README.md index bc4df85..de0897b 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ TokenStep 是一个 macOS 菜单栏 App,用来本地统计你在 Codex、Claud 下载最新版 DMG,打开后把 `TokenStep.app` 拖进「应用程序」即可使用: -[下载 TokenStep 最新版](https://github.com/Backtthefuture/TokenStep/releases/latest/download/TokenStep-0.2.19.dmg) +[下载 TokenStep 最新版](https://github.com/Backtthefuture/TokenStep/releases/latest/download/TokenStep-0.2.20.dmg) 也可以从 Release 页面查看所有版本: @@ -32,9 +32,9 @@ TokenStep 已使用 Developer ID 签名并通过 Apple 公证。首次打开时 Windows版本由十七做了移植,欢迎大家前往使用:https://github.com/canyexuanfan/TokenStep-Windows/releases -## 最新版本:0.2.19 +## 最新版本:0.2.20 -面板的额度和排名更精简:额度条粗细统一,排名只在前 10 名时显示前三名,并显示离你最近的后一名。0.2.16 起菜单栏面板改为一列紧凑面板,额度刷新更及时。详见 [0.2.19 发布说明](docs/RELEASE_NOTES_0.2.19.md)。 +额度窗口过了重置时间显示「已重置」并自动重新读取;面板一直开着时,倒计时、时间进度竖线和提前用完的提示也会每分钟更新;修复刘海卡片关闭菜单后意外收起、鼠标指针停在手形的问题。详见 [0.2.20 发布说明](docs/RELEASE_NOTES_0.2.20.md)。 历次更新(主题包、引力动效、自动更新等)见 [CHANGELOG.md](CHANGELOG.md)。 @@ -95,7 +95,7 @@ TokenStep 默认只做本地统计。 ## 安装方式 -1. 下载 [TokenStep 最新版 DMG](https://github.com/Backtthefuture/TokenStep/releases/latest/download/TokenStep-0.2.19.dmg)。 +1. 下载 [TokenStep 最新版 DMG](https://github.com/Backtthefuture/TokenStep/releases/latest/download/TokenStep-0.2.20.dmg)。 2. 打开 DMG。 3. 把 `TokenStep.app` 拖到「应用程序」。 4. 启动 TokenStep。 @@ -166,7 +166,7 @@ TokenStepSwift/dist/TokenStep.app 公开发布强制执行 Developer ID 签名、Apple 公证、票据装订、系统分发检查和隔离安装验证。不再生成可被误上传的“仅签名、未公证”发布包: ```bash -TOKENSTEP_VERSION=0.2.19 \ +TOKENSTEP_VERSION=0.2.20 \ CODE_SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)" \ TOKENSTEP_NOTARY_PROFILE="tokenstep-notary" \ ./script/package_release.sh --notarize diff --git a/docs/RELEASE_NOTES_0.2.20.md b/docs/RELEASE_NOTES_0.2.20.md new file mode 100644 index 0000000..2b854bf --- /dev/null +++ b/docs/RELEASE_NOTES_0.2.20.md @@ -0,0 +1,10 @@ +# TokenStep 0.2.20 + +面板里和时间有关的信息保持最新,修复几处细节问题。 + +- **额度到点重置**:额度窗口过了重置时间,改为显示「已重置」,不再显示旧的百分比;TokenStep 会在最近一次重置后约 30 秒自动重新读取额度,不用等到下一轮刷新。 +- **面板开着也会走时**:「17 分钟后」「1 分钟前」、额度条上的时间进度竖线和「会提前用完」的判断,面板一直开着时也会每分钟更新。 +- **刘海卡片**:在「…」菜单里选完操作后,如果鼠标停在卡片顶部,卡片不再意外收起。 +- **鼠标指针**:从榜单链接上关掉面板后,鼠标指针不再停留在手形。 + +Token、金额与额度的统计口径不变。 diff --git a/script/build_swiftui_and_run.sh b/script/build_swiftui_and_run.sh index ac50204..b69323d 100755 --- a/script/build_swiftui_and_run.sh +++ b/script/build_swiftui_and_run.sh @@ -23,7 +23,7 @@ HELPER_EXECUTABLE="$BUILD_DIR/$HELPER_NAME" ICON_FILE="$ROOT_DIR/TokenUsageMenuApp/assets/TokenStepIcon.icns" ODYSSEY_ASSET_DIR="$ROOT_DIR/TokenUsageMenuApp/assets/odyssey" INTERSTELLAR_ASSET_DIR="$ROOT_DIR/TokenUsageMenuApp/assets/interstellar" -VERSION="${TOKENSTEP_VERSION:-0.2.19}" +VERSION="${TOKENSTEP_VERSION:-0.2.20}" LAUNCH=true VERIFY=false