Conversation
…FORM_SPECIALIZATION - Remove LSRequiresIPhoneOS from Info.plist so macOS accepts the app bundle - Add ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES to Debug and Release build configs so Xcode uses the existing macos-arm64_x86_64 slice in llama.xcframework when linking for Mac Catalyst (no maccatalyst slice exists in the vendored build) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ios-arm64-maccatalyst slice to llama.xcframework (arm64 extracted from the macOS fat binary) so the Catalyst linker can find the framework - Make INFOPLIST_FILE unconditional in Debug and Release configs — the previous [sdk=iphone*] condition was skipped for Catalyst's macosx SDK, causing the code signing failure Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Patch llama Catalyst binary with vtool to set platform=MACCATALYST, removing the 'built for macOS' linker mismatch warning - Add platformFilters=(ios) to the Controls extension embed build file so Xcode skips embedding it on Catalyst (extension has SUPPORTS_MACCATALYST=NO so it never builds, causing 'no such file' when the main app tries to embed it) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a 'Mac Catalyst Build Notes' section to CLAUDE.md with step-by-step commands to recreate the ios-arm64-maccatalyst slice (lipo + vtool) whenever llama.xcframework is rebuilt. Also notes the textual fork Catalyst fix. Updates README.md llama.cpp entry with a concise Mac Catalyst warning. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mac Catalyst does not use shallow bundles — Xcode expects the framework binary at Versions/A/llama with top-level symlinks, not a flat iOS layout. Restructured ios-arm64-maccatalyst/llama.framework to match the macOS versioned convention and updated BinaryPath in xcframework Info.plist. Updated CLAUDE.md build instructions accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BisonNotes Share.appex is iOS-only and cannot be embedded in a macOS app. Add platformFilters=(ios) to its embed build file entry so Xcode skips it when building for Mac Catalyst. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BisonNotes AI Watch App.app is watchOS-only. Add platformFilters=(ios) so it is skipped when building for Mac Catalyst. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eenCovers NavigationView defaults to a two-column split layout on Mac Catalyst, which breaks scrolling inside modal sheets. Replacing it with NavigationStack restores single-column presentation everywhere. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On Mac Catalyst, SFSafariViewController is unavailable. Add the same onChange handler pattern used in SimpleSettingsView and RecordingsView so tapping the console buttons opens Safari instead of an empty sheet. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three changes: 1. On Mac Catalyst, use configureMixedAudioSession() instead of configureBackgroundRecording() — background audio modes are iOS-specific and the UIBackgroundModes check was blocking the audio session setup. 2. Add logging for the permission granted/denied result so failures are visible in the console. 3. Observe recorderVM.errorMessage in RecordingsView and surface it as an alert — previously errors like mic permission denied were silent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AVAudioApplication.requestRecordPermission does not trigger the macOS TCC privacy dialog. On Mac Catalyst, switch to AVCaptureDevice.requestAccess which properly integrates with System Settings → Privacy & Security → Microphone. Also handles the denied/restricted case with a clear error message, and uses configureMixedAudioSession instead of configureBackgroundRecording (background audio modes are iOS-only). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NavigationView defaults to a two-column split layout on Mac Catalyst, which breaks scrolling in any view presented modally. This completes the full codebase migration — SummaryDetailView, all settings views, TranscriptViews, RecordingsListView, BackgroundProcessingView, and all utility dialogs now use NavigationStack. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On Mac, quitting via the menu calls NSApplication.terminate: → exit(). ggml_metal_device's C++ static destructor then runs before Swift deinits, triggering GGML_ASSERT([rsets->data count] == 0) if Metal command buffers are still live. Add a UIApplication.willTerminateNotification observer in OnDeviceLLMEngine that calls service.unloadModel() before exit(), forcing llama_free and llama_model_free to run while the Metal runtime is still valid. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mark the function @mainactor so direct calls to didReceiveMicrophonePermission are valid, and replace DispatchQueue.main.async with Task { @mainactor in } in the AVCaptureDevice callback so the Swift concurrency actor hop is explicit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
startRecording() and startBackgroundRecording() are nonisolated, so they cannot call a @mainactor method directly. Wrap the call in Task { @mainactor in } to hop to the main actor explicitly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AVAudioSession.interruptionNotification and routeChangeNotification rely on Mach ports that don't exist on Mac, producing hundreds of "cannot add handler to 4/3 from 1 - dropping" log lines per session. Phone-call interruptions and Bluetooth routing are iOS-only concerns so these observers are simply skipped on Mac Catalyst. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AVAudioSession.requestRecordPermission is deprecated in Mac Catalyst 17. Switch to AVAudioApplication.shared.recordPermission / requestRecordPermission as the compiler recommends. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SwiftUI ScrollView is broken inside Mac Catalyst sheets — diagnostics
show HostingScrollView gets deallocated mid-interaction and presenting
controllers report as detached. Form (UITableView-backed) is the only
pattern that scrolls reliably, matching what AISettingsView already
does.
- Convert SettingsView, SummaryDetailView, TranscriptDetailView,
EditableTranscriptView, and AcknowledgementsView from ScrollView
bodies to NavigationStack { Form }
- Add Done toolbar button to BackgroundProcessingView (was unreachable
on Mac)
- Add .contentShape(Rectangle()) to Form button rows for full-row tap
targets
- Fix nested text/PDF attachment sheets in SummaryDetailView
- Add MacScrollDiagnosticView harness for verifying the fix from a
top-level (non-detached) presentation context
- Silence AVAudioSession Mach port warnings on Mac Catalyst by guarding
setCategory/setActive calls
- Add com.apple.security.device.audio-input entitlement for mic access
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SummaryDetailView.rebuildSummaryData no longer falls through to the legacy initializer when recordingId is nil. The legacy init generated a fresh UUID for `id`, which orphaned attachments/notes keyed off the original summary id. Make the id-preserving init's recordingId param optional and use it for both branches. - RecordingArchiveService now persists a security-scoped bookmark for archive destinations on Mac Catalyst (and stops using .minimalBookmark on iOS, which discarded the picker-granted scope). Resolution side matches with .withSecurityScope on Catalyst so a relaunch can still startAccessingSecurityScopedResource() against the saved location. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mac Catalyst recording was broken because AVAudioRecorder cannot negotiate the input format with CoreAudio without an AVAudioSession, producing AudioConverter -50 errors and partial/empty files. Replace the AVAudioRecorder code path on Catalyst with AVAudioEngine + AVAudioFile (new AudioRecorderViewModel+CatalystEngine.swift): the input node tap delivers PCM buffers that AVAudioFile encodes directly to AAC/M4A, matching the iOS on-disk format exactly. setCategory is re-enabled on Catalyst because the engine's input audio unit needs it to initialize (AUIOBase Initialize -50 otherwise). Add Pause/Resume to recording for all platforms: - iOS: AVAudioRecorder.pause()/.record() — single file - Catalyst: AVAudioEngine input tap remove/install — single file - ViewModel: pauseRecording(), resumeRecording(), isPaused - UI: side-by-side Pause+Stop / Resume+Stop buttons; "Paused" badge - Live transcription mode rejects pause (AVAudioEngine path can't cleanly pause without dropping the recognizer state) Gate iOS-only paths that produced log spam or cosmetic errors on Catalyst: - AVAudioSession property accesses (input selection, route info, restoreAudioSession, requestBackgroundAudioCapability) - UIDevice battery monitoring (PerformanceOptimizer, recording warnings, BackgroundProcessingManager diagnostics) - UIApplication.beginBackgroundTask + keep-alive silent audio in BackgroundProcessingManager and AudioRecorderViewModel - Recording-timer "unexpected stop" recovery that surfaced misleading "Microphone became unavailable" errors - Notification permission failures demoted from error to debug Also reset recordingState explicitly on start/stop so a paused state can't leak across recordings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Bump MARKETING_VERSION and CFBundleShortVersionString to 1.11 - Remove MacScrollDiagnosticView harness and its Scroll Tests entry now that Mac Catalyst sheet scrolling is fixed - In simple setup's Save & Configure, honor any already-selected local AI engine (On-Device LLM, MLX, or Apple Intelligence) along with its selected model; only fall back to On-Device LLM + Granite Micro when no local engine is configured Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Download and replace llama.xcframework with latest release b9134 - Create ios-arm64-maccatalyst slice from macOS arm64 binary - Apply vtool patch to set platform to MACCATALYST - Add maccatalyst entry to Info.plist - Update README-LLAMA-SETUP.md with version history and Mac Catalyst instructions Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Bump textual to 0c2c3b5 to pick up the bisonbet fork's Catalyst font fix. The previous pin (5b06b81) was upstream tip without the !targetEnvironment(macCatalyst) guards on the AppKit path, which caused FontDescriptor to resolve to NSFontDescriptor and broke TextStyleFontProvider (pointSize, withDesign, addingAttributes, SystemDesign, TraitKey.weight all unavailable on Catalyst). - MLXSwiftEngine: replace direct os_proc_available_memory() with Self.availableMemoryForModelLoad(). That API returns 0 on Mac Catalyst (no jetsam limits), which made the pre-load guard always fail. On Catalyst, read host_statistics64 / HOST_VM_INFO64 instead to compute free + inactive + speculative + purgeable page memory. - SummaryDetailView: add .buttonStyle(.borderless) to the Regenerate Summary, Edit/Add Location, and Delete buttons. They share one Form Section row, so without an explicit buttonStyle a tap on any one fires all three actions. That made tapping Regenerate also trigger the Delete Summary alert, which briefly flashed before the regen flow's dismiss() tore the view down. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Generate Transcript now lives on the recording (row + AudioPlayerView) and Generate Summary lives on the transcript (editor + second row button in the Transcripts tab). Buttons disappear once the artifact exists; regeneration happens from the existing detail views. A shared TranscriptionStarter service owns the audio-cleanup queue so all three entry points share one serial queue. Fixes a Mac Catalyst freeze on the Transcripts list by replacing per-row disk-touching calls (getAbsoluteURL, recording.summary relationship fault) with cheap attribute reads, and falls back to an all-inline list on Catalyst since the preview-+-"More" NavigationLink push wedged the responder chain on that platform. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
FluidAudioManager migrates to the new AsrManager(config:models:) single-step initializer and the decoder-state-based transcribe API (TdtDecoderState built from decoderLayerCount, passed inout). Pulls in swift-argument-parser and coordinated point upgrades across the AWS stack and Apple/community NIO/crypto packages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move provider API keys, AWS credentials, and Bedrock session tokens into Keychain-backed storage, migrate legacy UserDefaults values, clear process-wide AWS credential environment variables, and use explicit AWS SDK credential resolvers for Bedrock, Transcribe, and background jobs. Add endpoint validation for user-configurable AI services so public HTTP and WS endpoints are blocked by default while local/private endpoints remain allowed, with Development Mode warnings/toggles in OpenAI, OpenAI-compatible, Ollama, and Whisper settings. Require one-time share-extension import tokens before the main app scans the shared container, avoid logging full import URLs, protect shared import files, and disable iTunes file sharing. Enforce AWS Bedrock model response size limits before JSON parsing and strip raw control characters from Bedrock response data before decode. Apply explicit file protection to recordings, imported/restored audio, watch backups, transcript placeholder audio, attachment metadata/files, persistent error logs, and Core Data SQLite files. Set APNs entitlement through per-configuration APS_ENVIRONMENT values so Debug uses development and Release uses production. Default iCloud sensitive settings backups off, keep API keys and AWS credentials out of iCloud settings backups, and restore legacy sensitive backup values into Keychain when encountered. Add focused tests for endpoint policy, Keychain migration, and share import authorization, and update test helpers for current model/status APIs. Validation: git diff --cached --check; xcodebuild -project 'BisonNotes AI/BisonNotes AI.xcodeproj' -scheme 'BisonNotes AI' -configuration Debug -destination 'generic/platform=iOS Simulator' -derivedDataPath /private/tmp/BisonNotesAIDerivedData CODE_SIGNING_ALLOWED=NO build.
Update the README and the WordPress user guide (docs/bisonnotes-ai-guide.html) to reflect everything shipped in v1.11, and pick up minor dependency updates. Docs: - Mac Catalyst support, the AVAudioEngine recording pipeline, and the llama.xcframework Catalyst slice requirement - Pause and Resume recording across iOS, iPadOS, and Mac - Keychain-backed credential storage, file protection, endpoint validation, and Share Extension token authorization - The source-centric workflow (Generate Transcript on the recording, Generate Summary on the transcript) - MLX Swift experimental summarization engine and the experimental toggle - Watch complications, Control Center recording widget, summary attachments, recording title editing, Comedy Mode, and Google Calendar integration - Corrected the On-Device AI requirement (6 GB+ RAM, not iPhone 15 Pro+) - Fixed the "all 7 / all 8 AI engines" inconsistency in the user guide - Hero version badge bumped v1.8 -> v1.11; new Privacy & Security section added before Troubleshooting Dependencies (Package.resolved): - aws-sdk-swift 1.7.8 -> 1.7.9 - FluidAudio 0.14.7 -> 0.14.8 - smithy-swift 0.213.0 -> 0.214.0 - bisonbet/textual: tracked-branch revision bump Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 710bf006f0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return isPrivateIPv4(ipv4) | ||
| } | ||
|
|
||
| return normalized.hasPrefix("fc") || normalized.hasPrefix("fd") || normalized.hasPrefix("fe80:") |
There was a problem hiding this comment.
Validate IPv6 private literals before allowing HTTP
This prefix check treats any hostname beginning with fc or fd as a private IPv6 address, so a public DNS name such as http://fd-example.com bypasses the new HTTP block and can receive recordings/transcripts in cleartext without Development Mode. Please parse IPv6 literals explicitly (after removing brackets) and only allow actual ULA/link-local addresses.
Useful? React with 👍 / 👎.
| import Foundation | ||
|
|
||
| enum AppFileProtection { | ||
| static let sensitiveFileProtection: FileProtectionType = .complete |
There was a problem hiding this comment.
Preserve access for locked-device background work
Using .complete as the global protection class is applied to newly recorded/imported audio and the Core Data store, but .complete makes files unavailable whenever the device is locked. In this app's background recording/processing flows, any transcription/cleanup or metadata save that opens these files after the user locks the phone can fail even though background audio/tasks are intended to keep running; use a class such as complete-until-first-authentication unless the flow is strictly foreground-only.
Useful? React with 👍 / 👎.
Summary
v1.11 is a substantial release. The biggest single change is that the app now ships on Mac via Mac Catalyst, but the cycle also includes security/credential hardening, a source-centric transcript/summary workflow, the experimental MLX Swift engine, and a number of pre-v1.11 features (archive to iCloud, summary attachments, recording title editing, watch complications, Google Calendar integration).
Highlights
AVAudioEnginepipeline, settings sheets are Form-based for reliable scrolling, navigation switched toNavigationStackeverywhere, llama.cpp ships with a manually built Catalyst slice, and the llama Metal crash on quit is fixed.APS_ENVIRONMENT.Also included in this cycle
Docs
README.mdrewritten to cover all of the above and corrected the On-Device AI requirement (6 GB+ RAM, not iPhone 15 Pro+).docs/bisonnotes-ai-guide.html(WordPress user guide) bumped to v1.11, new "What's new", "Mac Catalyst", "Pause & Resume", "Privacy & Security", and "MLX Swift" sections; first-transcript/first-summary instructions updated for the new workflow; "all 7 / all 8 engines" inconsistency fixed.Dependencies
Test plan
UserDefaults🤖 Generated with Claude Code