Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d76c7496bb | ||
|
|
45a3925f65 | ||
|
|
113e91583d | ||
|
|
c60fb93e1a | ||
|
|
b16c24cb44 | ||
|
|
89a79c6f13 | ||
|
|
aa529835f7 | ||
|
|
b5c3cc6379 | ||
|
|
c8adaf1baf | ||
|
|
3e99e1417e | ||
|
|
e41d3d0c0f | ||
|
|
1078951c43 | ||
|
|
ed6a5e1b51 | ||
|
|
8701fb1326 | ||
|
|
247b5fc202 | ||
|
|
255de797f8 | ||
|
|
0046c53283 | ||
|
|
8d222d63f0 | ||
|
|
5720370649 |
@@ -60,6 +60,11 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
.sink { [weak self] _ in self?.updateStatusItem() }
|
.sink { [weak self] _ in self?.updateStatusItem() }
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
updateManager.$updateAvailable
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] _ in self?.updateStatusItem() }
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
// Refresh timer
|
// Refresh timer
|
||||||
scheduleTimer(interval: usageViewModel.refreshInterval)
|
scheduleTimer(interval: usageViewModel.refreshInterval)
|
||||||
|
|
||||||
@@ -166,7 +171,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
let sdStr = sd.map { "\(Int($0.usedPercent.rounded()))%" } ?? "—"
|
let sdStr = sd.map { "\(Int($0.usedPercent.rounded()))%" } ?? "—"
|
||||||
button.image = nil
|
button.image = nil
|
||||||
button.attributedTitle = NSAttributedString(string: "")
|
button.attributedTitle = NSAttributedString(string: "")
|
||||||
button.title = "◔ \(fhStr) \(sdStr)"
|
let updatePrefix = updateManager.updateAvailable ? "↑ Update " : "◔ "
|
||||||
|
button.title = "\(updatePrefix)\(fhStr) \(sdStr)"
|
||||||
|
button.font = NSFont.monospacedDigitSystemFont(ofSize: 12, weight: .medium)
|
||||||
|
} else if updateManager.updateAvailable {
|
||||||
|
button.image = nil
|
||||||
|
button.attributedTitle = NSAttributedString(string: "")
|
||||||
|
button.title = "↑ Update"
|
||||||
button.font = NSFont.monospacedDigitSystemFont(ofSize: 12, weight: .medium)
|
button.font = NSFont.monospacedDigitSystemFont(ofSize: 12, weight: .medium)
|
||||||
} else {
|
} else {
|
||||||
button.title = ""
|
button.title = ""
|
||||||
@@ -199,6 +210,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
@objc func showPopover() {
|
@objc func showPopover() {
|
||||||
guard let button = statusItem?.button, let popover else { return }
|
guard let button = statusItem?.button, let popover else { return }
|
||||||
if !popover.isShown {
|
if !popover.isShown {
|
||||||
|
NotificationCenter.default.post(name: .popoverWillOpen, object: nil)
|
||||||
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
|
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
}
|
}
|
||||||
@@ -209,6 +221,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
if popover.isShown {
|
if popover.isShown {
|
||||||
popover.performClose(nil)
|
popover.performClose(nil)
|
||||||
} else {
|
} else {
|
||||||
|
NotificationCenter.default.post(name: .popoverWillOpen, object: nil)
|
||||||
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
|
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,9 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
.frame(width: 480)
|
.frame(width: 480)
|
||||||
.animation(.easeInOut(duration: 0.2), value: showSettings)
|
.animation(.easeInOut(duration: 0.2), value: showSettings)
|
||||||
|
.onReceive(NotificationCenter.default.publisher(for: .popoverWillOpen)) { _ in
|
||||||
|
showSettings = false
|
||||||
|
}
|
||||||
.onReceive(NotificationCenter.default.publisher(for: .openUpdateSheet)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: .openUpdateSheet)) { _ in
|
||||||
showSettings = false
|
showSettings = false
|
||||||
showUpdateSheet = true
|
showUpdateSheet = true
|
||||||
@@ -977,6 +980,36 @@ struct UpdateSuccessBanner: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Markdown notes renderer
|
||||||
|
|
||||||
|
struct ReleaseNotesText: View {
|
||||||
|
let text: String
|
||||||
|
var font: Font = .system(size: 12)
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
let processed = text
|
||||||
|
.split(separator: "\n", omittingEmptySubsequences: false)
|
||||||
|
.map { line -> String in
|
||||||
|
let s = String(line)
|
||||||
|
if s.hasPrefix("### ") { return "**\(s.dropFirst(4))**" }
|
||||||
|
if s.hasPrefix("## ") { return "**\(s.dropFirst(3))**" }
|
||||||
|
if s.hasPrefix("# ") { return "**\(s.dropFirst(2))**" }
|
||||||
|
if s.hasPrefix("- ") { return "• \(s.dropFirst(2))" }
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
.joined(separator: "\n")
|
||||||
|
|
||||||
|
if let attr = try? AttributedString(markdown: processed,
|
||||||
|
options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace)) {
|
||||||
|
Text(attr).font(font).foregroundColor(.secondary)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
} else {
|
||||||
|
Text(text).font(font).foregroundColor(.secondary)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Update Banner
|
// MARK: - Update Banner
|
||||||
|
|
||||||
struct UpdateBanner: View {
|
struct UpdateBanner: View {
|
||||||
@@ -992,22 +1025,13 @@ struct UpdateBanner: View {
|
|||||||
Text("Update available — v\(updater.latestVersion)")
|
Text("Update available — v\(updater.latestVersion)")
|
||||||
.font(.system(size: 12, weight: .semibold))
|
.font(.system(size: 12, weight: .semibold))
|
||||||
if !updater.releaseNotes.isEmpty {
|
if !updater.releaseNotes.isEmpty {
|
||||||
Text(updater.releaseNotes)
|
ReleaseNotesText(text: updater.releaseNotes, font: .system(size: 11))
|
||||||
.font(.system(size: 11))
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Update") { showSheet = true }
|
Button("Update") { showSheet = true }
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(.borderedProminent)
|
||||||
.controlSize(.small)
|
.controlSize(.small)
|
||||||
Button { updater.updateAvailable = false } label: {
|
|
||||||
Image(systemName: "xmark")
|
|
||||||
.font(.system(size: 10, weight: .medium))
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 16)
|
.padding(.horizontal, 16)
|
||||||
.padding(.vertical, 10)
|
.padding(.vertical, 10)
|
||||||
@@ -1061,9 +1085,7 @@ struct UpdateSheet: View {
|
|||||||
.foregroundColor(.blue)
|
.foregroundColor(.blue)
|
||||||
}
|
}
|
||||||
if !updater.releaseNotes.isEmpty {
|
if !updater.releaseNotes.isEmpty {
|
||||||
Text(updater.releaseNotes)
|
ReleaseNotesText(text: updater.releaseNotes)
|
||||||
.font(.system(size: 12))
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0.5</string>
|
<string>1.1.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>24</string>
|
<string>33</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>13.0</string>
|
<string>13.0</string>
|
||||||
<key>LSUIElement</key>
|
<key>LSUIElement</key>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class UpdateNotificationWindowController: NSWindowController {
|
|||||||
|
|
||||||
// Called when a new update is detected while running
|
// Called when a new update is detected while running
|
||||||
static func showUpdate(version: String, notes: String, near statusItem: NSStatusItem?) {
|
static func showUpdate(version: String, notes: String, near statusItem: NSStatusItem?) {
|
||||||
showWindow(version: version, subtitle: notes.isEmpty ? "Tap to update now." : notes, near: statusItem, isUpdate: true)
|
showWindow(version: version, subtitle: "Tap to view release notes & update now!", near: statusItem, isUpdate: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func showLimitWarning(windowName: String, percent: Int, near statusItem: NSStatusItem?) {
|
static func showLimitWarning(windowName: String, percent: Int, near statusItem: NSStatusItem?) {
|
||||||
|
|||||||
@@ -307,6 +307,7 @@ extension Notification.Name {
|
|||||||
static let refreshIntervalChanged = Notification.Name("refreshIntervalChanged")
|
static let refreshIntervalChanged = Notification.Name("refreshIntervalChanged")
|
||||||
static let closePopover = Notification.Name("closePopover")
|
static let closePopover = Notification.Name("closePopover")
|
||||||
static let showPopover = Notification.Name("showPopover")
|
static let showPopover = Notification.Name("showPopover")
|
||||||
|
static let popoverWillOpen = Notification.Name("popoverWillOpen")
|
||||||
static let updateDetected = Notification.Name("updateDetected")
|
static let updateDetected = Notification.Name("updateDetected")
|
||||||
static let limitWarning = Notification.Name("limitWarning")
|
static let limitWarning = Notification.Name("limitWarning")
|
||||||
static let limitReset = Notification.Name("limitReset")
|
static let limitReset = Notification.Name("limitReset")
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
[](https://www.apple.com/macos/)
|
[](https://www.apple.com/macos/)
|
||||||
[](https://swift.org)
|
[](https://swift.org)
|
||||||
[](https://github.com/superdooper86/claudechecker/releases)
|
[](https://github.com/superdooper86/claudechecker/releases)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+14
-4
@@ -1,6 +1,16 @@
|
|||||||
## What's new in v1.0.5
|
## What's new in v1.1.0
|
||||||
|
|
||||||
### Release notes now render correctly
|
### Clickable notifications
|
||||||
The update sheet and in-app banner now render markdown formatting in release notes — headers, bold text, and bullet points all display correctly instead of showing raw `##` and `**` characters.
|
Tapping a limit warning or reset notification opens the main window. Tapping an update notification opens the update sheet directly — no need to manually click the menubar icon.
|
||||||
|
|
||||||
The update notification popup no longer shows the full release notes text; it shows a concise "Tap to update now." prompt instead, keeping it compact.
|
### Update indicator in menubar
|
||||||
|
When an update is available, the menubar shows **↑ Update** alongside your usage percentages so you know at a glance without opening the app.
|
||||||
|
|
||||||
|
### Release notes render correctly
|
||||||
|
The update banner and update sheet now render markdown formatting — headers, **bold text**, and bullet points display properly.
|
||||||
|
|
||||||
|
### Update notification improved
|
||||||
|
The update popup now reads **"Tap to view release notes & update now!"** making it clear you can read what changed before installing.
|
||||||
|
|
||||||
|
### Main window always opens on click
|
||||||
|
Closing the app while Settings was open would cause the next click to reopen straight to Settings. The app now always returns to the main window.
|
||||||
|
|||||||
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0.4",
|
"version": "1.0.12",
|
||||||
"url": "https://github.com/superdooper86/claudechecker/releases/download/v1.0.4/ClaudeChecker.zip",
|
"url": "https://github.com/superdooper86/claudechecker/releases/download/v1.0.12/ClaudeChecker.zip",
|
||||||
"notes": "## What's new in v1.0.4\n\n### Clickable notifications\nNotifications are now interactive — tap to act, not just read:\n\n- **Limit warnings & resets** (80%, 95%, reset) — tapping the notification opens the main ClaudeChecker window so you can see your current usage at a glance\n- **Update available** — tapping the notification opens the main window with the update sheet already showing, so you can install in one click\n\nPreviously notifications were display-only and required manually clicking the menubar icon to take action. This removes that friction."
|
"notes": "## What's new in v1.0.12\n\n### Update indicator in menubar\nWhen an update is available, the menubar now shows **↑ Update** alongside your usage percentages — so you can see there's an update without opening the app."
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user