Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
00c19cb
Add summary attachments and note export support
bisonbet Apr 15, 2026
7672b4c
Add recording title editing from audio and transcript views
bisonbet Apr 15, 2026
480ec1c
Fix recording title editor bugs and refactor shared component
github-actions[bot] Apr 15, 2026
d3bfcdf
Fix review issues: backward compat decode, file leak, main-thread rea…
github-actions[bot] Apr 15, 2026
ff7323e
Merge pull request #83 from bisonbet/codex/add-file-attachment-featur…
bisonbet Apr 15, 2026
3b31d05
Redact private data from logs and improve note/attachment lifecycle
bisonbet Apr 16, 2026
3325d2e
Add archive-to-cloud feature for audio recordings
bisonbet Apr 19, 2026
6626c54
Fix audio offloading bug: preserve archived recordings in list and tr…
claude Apr 19, 2026
c747edd
Address Codex review: deterministic summary picker and robust URL res…
bisonbet Apr 19, 2026
9e0827d
Merge pull request #86 from bisonbet/claude/fix-audio-offloading-bug-…
bisonbet Apr 19, 2026
49e7019
Add tokenized archive exports and restore-on-reimport flow
bisonbet Apr 20, 2026
6ea1f84
Archive audio to tracked iCloud Drive locations
bisonbet Apr 22, 2026
6a16081
Persist explicit recording start timestamps and use them for recordin…
bisonbet Apr 23, 2026
eaf90b7
Clean up .recordingmeta sidecars on deletion and fix display name tim…
bisonbet Apr 23, 2026
e219056
Add clean audio export with user-friendly filenames
bisonbet Apr 23, 2026
b6c66b6
Scope recordingStartedAt timestamp to its recording URL
github-actions[bot] Apr 23, 2026
f9c0ee5
Merge pull request #87 from bisonbet/codex/add-date-and-time-tracking…
bisonbet Apr 23, 2026
406a209
Add MLX Swift on-device summarization engine with memory-safe iOS inf…
bisonbet Apr 24, 2026
ecf7e5b
Fix experimental mode toggle to fully disable MLX and handle active e…
bisonbet Apr 26, 2026
da4ad78
Swap Transcripts and Summaries order in iPad sidebar
bisonbet Apr 28, 2026
fbe81aa
Grant write permissions to @claude mention workflow
bisonbet May 9, 2026
86123c8
Restore write permissions for Claude Code Review workflow
bisonbet May 9, 2026
1b69442
Revert workflow permissions and enable display_report
bisonbet May 9, 2026
7b99e09
Post Claude code review as a sticky PR comment
bisonbet May 9, 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
4 changes: 4 additions & 0 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ jobs:
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
plugins: 'code-review@claude-code-plugins'
prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
# Post (and update) a single sticky comment on the PR with the review summary.
use_sticky_comment: true
# Also write the report to the GitHub Actions step summary as a backup.
display_report: true
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options

24 changes: 13 additions & 11 deletions BisonNotes AI/BisonNotes AI Watch App/WatchLocationManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import Foundation
@preconcurrency import CoreLocation
import Combine
import os.log

/// Location manager for Apple Watch to collect location data during recordings
@MainActor
Expand All @@ -23,6 +24,7 @@ class WatchLocationManager: NSObject, ObservableObject {
private let locationManager = CLLocationManager()
private var locationCompletion: ((CLLocation?) -> Void)?
private var isRequestingLocation = false
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.bisonnotes.watchapp", category: "Location")

override init() {
super.init()
Expand All @@ -44,28 +46,28 @@ class WatchLocationManager: NSObject, ObservableObject {

/// Request location permission from user
func requestLocationPermission() {
print("📍⌚ Requesting location permission on watch...")
logger.debug("Requesting location permission on watch")
locationManager.requestWhenInUseAuthorization()
}

/// Get current location for recording
func getCurrentLocation(completion: @escaping (CLLocation?) -> Void) {
guard isLocationAvailable else {
print("📍⌚ Location not available")
logger.debug("Location not available")
completion(nil)
return
}

// If we have a recent location (less than 30 seconds old), use it
if let currentLocation = currentLocation,
currentLocation.timestamp.timeIntervalSinceNow > -30 {
print("📍⌚ Using cached location")
logger.debug("Using cached location")
completion(currentLocation)
return
}

// Request fresh location
print("📍⌚ Requesting fresh location...")
logger.debug("Requesting fresh location")
locationCompletion = completion
isRequestingLocation = true
locationManager.requestLocation()
Expand All @@ -75,13 +77,13 @@ class WatchLocationManager: NSObject, ObservableObject {
func startLocationUpdates() {
guard isLocationAvailable else { return }

print("📍⌚ Starting location monitoring...")
logger.debug("Starting location monitoring")
locationManager.startUpdatingLocation()
}

/// Stop monitoring location changes
func stopLocationUpdates() {
print("📍⌚ Stopping location monitoring...")
logger.debug("Stopping location monitoring")
locationManager.stopUpdatingLocation()
isRequestingLocation = false
locationCompletion = nil
Expand All @@ -95,7 +97,7 @@ class WatchLocationManager: NSObject, ObservableObject {
let servicesEnabled = CLLocationManager.locationServicesEnabled()
await MainActor.run {
self.isLocationAvailable = (self.authorizationStatus == .authorizedWhenInUse || self.authorizationStatus == .authorizedAlways) && servicesEnabled
print("📍⌚ Location availability updated: \(self.isLocationAvailable)")
self.logger.debug("Location availability updated: \(self.isLocationAvailable, privacy: .public)")
}
}
}
Expand All @@ -109,7 +111,7 @@ class WatchLocationManager: NSObject, ObservableObject {
isRequestingLocation = false
}

print("📍⌚ Location updated: \(location.coordinate.latitude), \(location.coordinate.longitude), accuracy: \(location.horizontalAccuracy)m")
logger.debug("Location updated, accuracy: \(location.horizontalAccuracy, privacy: .public)m")
}

private func handleLocationError(_ error: Error) {
Expand All @@ -121,7 +123,7 @@ class WatchLocationManager: NSObject, ObservableObject {
isRequestingLocation = false
}

print("📍⌚ Location error: \(error.localizedDescription)")
logger.error("Location error: \(error.localizedDescription, privacy: .public)")
}
}

Expand All @@ -134,7 +136,7 @@ extension WatchLocationManager: CLLocationManagerDelegate {

// Filter out invalid or inaccurate locations
guard location.horizontalAccuracy < 100 && location.horizontalAccuracy > 0 else {
print("📍⌚ Location accuracy too low: \(location.horizontalAccuracy)m")
Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.bisonnotes.watchapp", category: "Location").debug("Location accuracy too low: \(location.horizontalAccuracy, privacy: .public)m")
return
}

Expand All @@ -150,7 +152,7 @@ extension WatchLocationManager: CLLocationManagerDelegate {
}

nonisolated func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
print("📍⌚ Location authorization changed to: \(status.rawValue)")
Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.bisonnotes.watchapp", category: "Location").debug("Location authorization changed to: \(status.rawValue, privacy: .public)")

Task { @MainActor in
authorizationStatus = status
Expand Down
40 changes: 22 additions & 18 deletions BisonNotes AI/BisonNotes AI Watch App/WatchRecordingStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import Foundation
import CryptoKit
import Combine
import os.log

#if canImport(WatchKit)
import WatchKit
Expand All @@ -22,6 +23,9 @@ class WatchRecordingStorage: ObservableObject {
@Published var storageUsed: Int64 = 0
@Published var availableStorage: Int64 = 0

// MARK: - Logging
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.bisonnotes.watchapp", category: "RecordingStorage")

// MARK: - Private Properties
private let fileManager = FileManager.default
private let recordingsDirectoryName = "WatchRecordings"
Expand Down Expand Up @@ -64,9 +68,9 @@ class WatchRecordingStorage: ObservableObject {
try fileManager.createDirectory(at: recordingsSubdirectoryURL,
withIntermediateDirectories: true,
attributes: nil)
print("⌚ Storage directories created successfully")
logger.debug("Storage directories created successfully")
} catch {
print("❌ Failed to create storage directories: \(error)")
logger.error("Failed to create storage directories: \(error.localizedDescription, privacy: .public)")
}
}

Expand Down Expand Up @@ -109,11 +113,11 @@ class WatchRecordingStorage: ObservableObject {
// Clean up old recordings if needed
performStorageCleanup()

print("✅ Saved recording: \(filename) (\(fileSize) bytes)")
logger.debug("Saved recording (\(fileSize, privacy: .public) bytes)")
return metadata

} catch {
print("❌ Failed to save recording: \(error)")
logger.error("Failed to save recording: \(error.localizedDescription, privacy: .public)")
return nil
}
}
Expand All @@ -137,10 +141,10 @@ class WatchRecordingStorage: ObservableObject {
saveRecordingsMetadata()
updateStorageInfo()

print("🗑 Deleted recording: \(recording.filename)")
logger.debug("Deleted recording")

} catch {
print("❌ Failed to delete recording: \(error)")
logger.error("Failed to delete recording: \(error.localizedDescription, privacy: .public)")
}
}

Expand All @@ -157,7 +161,7 @@ class WatchRecordingStorage: ObservableObject {
localRecordings[index] = metadata
saveRecordingsMetadata()

print("📊 Updated sync status for \(metadata.filename): \(status.rawValue)")
logger.debug("Updated sync status: \(status.rawValue, privacy: .public)")
}
}

Expand Down Expand Up @@ -190,10 +194,10 @@ class WatchRecordingStorage: ObservableObject {
// Verify files still exist and clean up orphaned metadata
cleanupOrphanedMetadata()

print("📱 Loaded \(localRecordings.count) recordings from metadata")
logger.debug("Loaded \(self.localRecordings.count, privacy: .public) recordings from metadata")

} catch {
print("❌ Failed to load recordings metadata: \(error)")
logger.error("Failed to load recordings metadata: \(error.localizedDescription, privacy: .public)")
localRecordings = []
}
}
Expand All @@ -202,9 +206,9 @@ class WatchRecordingStorage: ObservableObject {
do {
let data = try JSONEncoder().encode(localRecordings)
try data.write(to: metadataFileURL)
print("💾 Saved recordings metadata")
logger.debug("Saved recordings metadata")
} catch {
print("❌ Failed to save recordings metadata: \(error)")
logger.error("Failed to save recordings metadata: \(error.localizedDescription, privacy: .public)")
}
}

Expand All @@ -215,14 +219,14 @@ class WatchRecordingStorage: ObservableObject {
let fileURL = recordingsSubdirectoryURL.appendingPathComponent(metadata.filename)
let exists = fileManager.fileExists(atPath: fileURL.path)
if !exists {
print("🧹 Removing orphaned metadata for: \(metadata.filename)")
logger.debug("Removing orphaned recording metadata")
}
return exists
}

if localRecordings.count != originalCount {
saveRecordingsMetadata()
print("🧹 Cleaned up \(originalCount - localRecordings.count) orphaned metadata entries")
logger.debug("Cleaned up \(originalCount - self.localRecordings.count, privacy: .public) orphaned metadata entries")
}
}

Expand All @@ -242,7 +246,7 @@ class WatchRecordingStorage: ObservableObject {
availableStorage = min(freeSpace, maxStorageUsage - storageUsed)

} catch {
print("❌ Failed to get storage info: \(error)")
logger.error("Failed to get storage info: \(error.localizedDescription, privacy: .public)")
availableStorage = max(0, maxStorageUsage - storageUsed)
}
}
Expand All @@ -266,7 +270,7 @@ class WatchRecordingStorage: ObservableObject {
}

private func performAutomaticCleanup() {
print("🧹 Performing automatic storage cleanup...")
logger.debug("Performing automatic storage cleanup")

// First, remove synced recordings (oldest first)
let syncedRecordings = getSyncedRecordings()
Expand Down Expand Up @@ -299,7 +303,7 @@ class WatchRecordingStorage: ObservableObject {
}
}

print("🧹 Cleanup complete. Storage: \(storageUsed) bytes, Recordings: \(localRecordings.count)")
logger.debug("Cleanup complete. Storage: \(self.storageUsed, privacy: .public) bytes, Recordings: \(self.localRecordings.count, privacy: .public)")
}

// MARK: - Utilities
Expand All @@ -313,7 +317,7 @@ class WatchRecordingStorage: ObservableObject {
let digest = Insecure.MD5.hash(data: data)
return digest.map { String(format: "%02hhx", $0) }.joined()
} catch {
print("❌ Failed to calculate checksum: \(error)")
logger.error("Failed to calculate checksum: \(error.localizedDescription, privacy: .public)")
return nil
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import Foundation
import SwiftUI
import Combine
import os.log

#if canImport(WatchKit)
import WatchKit
Expand Down Expand Up @@ -537,7 +538,7 @@ class WatchRecordingViewModel: ObservableObject {
accuracy: location.horizontalAccuracy
)
self?.recordingStartLocation = watchLocationData
print("📍⌚ Captured recording location: \(location.coordinate.latitude), \(location.coordinate.longitude)")
Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.bisonnotes.watchapp", category: "Recording").debug("Captured recording location, accuracy: \(location.horizontalAccuracy, privacy: .public)m")
} else {
print("📍⌚ Failed to get recording location")
}
Expand Down Expand Up @@ -655,7 +656,7 @@ class WatchRecordingViewModel: ObservableObject {
return
}

print("⌚ Starting sync for recording: \(recording.filename)")
Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.bisonnotes.watchapp", category: "Recording").debug("Starting sync for recording")
startRecordingSync(recording)
}

Expand Down Expand Up @@ -683,7 +684,7 @@ class WatchRecordingViewModel: ObservableObject {
return
}

print("⌚ Recording completed and saved locally: \(metadata.filename)")
Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.bisonnotes.watchapp", category: "Recording").debug("Recording completed and saved locally")

// Update state
recordingState = .processing
Expand Down
Loading
Loading