TIDY-453 - Swift apps in monorepo#549
Conversation
There was a problem hiding this comment.
Sorry @thyaravind, your pull request is larger than the review limit of 150000 diff characters
📝 WalkthroughWalkthroughThis pull request introduces comprehensive iOS native support across three new native applications (Nucleum, Memotron, Pointron) with a shared NucleumNativeKit framework, complete Xcode project configurations, app lifecycle management, audio transcription integration via Whisper C++, Core Data persistence, WidgetKit extensions, and build automation tooling for iOS web bundle generation. Changes
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
|
Oops! Your Typo free trial has ended. If you'd like to continue receiving code reviews, please ask the admin to check out our special pricing plan. |
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 23ec91ca61
ℹ️ 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".
| if (!(await exists(xcframeworkPath))) { | ||
| await run("./build-xcframework.sh", [], { cwd: whisperDir }); |
There was a problem hiding this comment.
Rebuild whisper framework after checking out a new ref
After git checkout ref, the script only runs build-xcframework.sh when build-apple/whisper.xcframework is missing. If developers rerun this with a different WHISPER_CPP_REF, the existing xcframework is reused even though sources changed, so native builds silently link stale binaries from the old commit. This breaks reproducibility and can ship/test against the wrong whisper version.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
40 issues found across 258 files
Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed. cubic prioritises the most important files to review.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/SafariWebview.swift">
<violation number="1" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/SafariWebview.swift:55">
P1: Force-unwrapping `URL(string: urlString)` can crash when `urlString` is empty or invalid (it is currently initialized to an empty string).</violation>
<violation number="2" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/SafariWebview.swift:60">
P1: Custom agent: **Flag Security Vulnerabilities**
Authentication callback/error details are logged directly, exposing sensitive sign-in artifacts.</violation>
<violation number="3" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/SafariWebview.swift:87">
P2: Starting `ASWebAuthenticationSession` inside `updateUIViewController` unconditionally can launch duplicate authentication flows on SwiftUI updates.</violation>
</file>
<file name="ios/nucleum/Nucleum/local/LocalConfig.swift">
<violation number="1" location="ios/nucleum/Nucleum/local/LocalConfig.swift:13">
P1: `appDataUrl` is initialized with an empty URL string, so it is always `nil` and will crash when force-unwrapped in `fetchAppData()`.</violation>
</file>
<file name="ios/memotron/Memotron for Mac/Memotron_for_MacApp.swift">
<violation number="1" location="ios/memotron/Memotron for Mac/Memotron_for_MacApp.swift:103">
P3: Remove the unused draggable window/delegate types; they are not wired into the app and add dead code maintenance overhead.</violation>
</file>
<file name="ios/nucleum/Nucleum/Info.plist">
<violation number="1" location="ios/nucleum/Nucleum/Info.plist:21">
P1: `NSAllowsArbitraryLoads` is enabled globally, which disables ATS and permits insecure network traffic across the app.</violation>
</file>
<file name="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Job.swift">
<violation number="1" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Job.swift:44">
P2: `JobResult` is public, but its properties are internal by default, so external modules cannot read any of its data.</violation>
</file>
<file name="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/MacAppDelegate.swift">
<violation number="1" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/MacAppDelegate.swift:20">
P2: Handle all incoming URLs in `application(_:open:)` instead of only the first one.</violation>
<violation number="2" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/MacAppDelegate.swift:22">
P1: Avoid logging full callback URLs; redact or remove these prints to prevent sensitive data exposure in logs.</violation>
</file>
<file name="ios/nucleum/Nucleum for Mac/Nucleum_for_MacApp.swift">
<violation number="1" location="ios/nucleum/Nucleum for Mac/Nucleum_for_MacApp.swift:103">
P3: `AppDelegateTwo` and its custom window flow are dead code because the app is configured to use `MacAppDelegate`, not this delegate.</violation>
<violation number="2" location="ios/nucleum/Nucleum for Mac/Nucleum_for_MacApp.swift:146">
P2: Custom agent: **Check System Design and Architectural Patterns**
Adapter code manually invokes `windowDidLoad()`, coupling view-layer code to controller lifecycle internals and violating framework lifecycle ownership.</violation>
</file>
<file name="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/Modal.swift">
<violation number="1" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/Modal.swift:31">
P1: Avoid force-unwrapping the constructed URL; invalid popup paths can cause a runtime crash when opening the modal.</violation>
</file>
<file name="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/WebView/WebViewCommon.swift">
<violation number="1" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/WebView/WebViewCommon.swift:25">
P2: Avoid force-unwrapping `request.url`; handle nil with a guarded failure to prevent a runtime crash.</violation>
<violation number="2" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/WebView/WebViewCommon.swift:44">
P1: Validate and constrain the requested path before reading from disk to prevent directory traversal outside the bundled web root.</violation>
<violation number="3" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/WebView/WebViewCommon.swift:118">
P1: Custom agent: **Flag Security Vulnerabilities**
Public URL creation does not enforce HTTPS, allowing insecure HTTP URLs to be loaded.</violation>
</file>
<file name="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/WebView/CustomWebView.swift">
<violation number="1" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/WebView/CustomWebView.swift:31">
P1: Custom agent: **Flag Security Vulnerabilities**
WebView loads unvalidated URLs without enforcing HTTPS, allowing insecure HTTP requests.</violation>
<violation number="2" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/WebView/CustomWebView.swift:35">
P2: Avoid unconditional reloads in `updateUIView`; only call `load` when the target URL actually changed.</violation>
</file>
<file name="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/CoreDataStack.swift">
<violation number="1" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/CoreDataStack.swift:14">
P1: Do not swallow persistent store load failures; fail fast or propagate the error so the app does not run with an unusable Core Data stack.</violation>
<violation number="2" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/CoreDataStack.swift:107">
P2: `save()` currently hides write failures from callers; propagate or surface the error instead of only logging it.</violation>
</file>
<file name="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/elements/SimpleWidgetText.swift">
<violation number="1" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/elements/SimpleWidgetText.swift:4">
P2: Do not use `@State` for initializer inputs; this can prevent the text/color from updating correctly when parent values change.</violation>
</file>
<file name="ios/memotron/Memotron/ContentView.swift">
<violation number="1" location="ios/memotron/Memotron/ContentView.swift:19">
P2: The transcription debugger UI is unreachable because `isShowTranscriptionDebugger` is always false in this view.</violation>
</file>
<file name="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/LoadingOverlay.swift">
<violation number="1" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/LoadingOverlay.swift:18">
P2: `isSheet` is declared as `@State` but never mutated, making the `isSheet ? "loading..." : ...` branch effectively dead in this view. Use a regular stored property (or remove the condition) so the value can be provided by the parent.</violation>
</file>
<file name="ios/nucleum/Nucleum/whisper/WhisperCppWrapper.h">
<violation number="1" location="ios/nucleum/Nucleum/whisper/WhisperCppWrapper.h:15">
P2: Initializer nullability is incorrect: the implementation can return `nil`, but the header declares a nonnull return under `NS_ASSUME_NONNULL`.</violation>
<violation number="2" location="ios/nucleum/Nucleum/whisper/WhisperCppWrapper.h:21">
P1: Transcription method nullability is wrong: methods can return `nil` and accept `nil` error pointers, but the header declares them as nonnull.</violation>
</file>
<file name="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Extensions.swift">
<violation number="1" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Extensions.swift:99">
P2: Require exactly 3 JWT segments before decoding; current check accepts malformed token formats.</violation>
</file>
<file name="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/Offline.swift">
<violation number="1" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/Offline.swift:22">
P2: Inject `AppStore` into the SwiftUI preview; otherwise the preview crashes due to missing `EnvironmentObject`.</violation>
</file>
<file name="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/CameraTestView.swift">
<violation number="1" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/CameraTestView.swift:131">
P2: `AVCaptureSession.startRunning()` is being called on the main thread, which can block UI rendering during camera startup.</violation>
</file>
<file name="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/elements/SimpleText.swift">
<violation number="1" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/elements/SimpleText.swift:11">
P1: Do not use `@State` for initializer-provided display props; this can desynchronize the view from parent updates.</violation>
<violation number="2" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/elements/SimpleText.swift:14">
P2: Custom agent: **Check System Design and Architectural Patterns**
`SimpleText` is tightly coupled to global `AppStore` state, creating improper layering for a reusable UI element.</violation>
<violation number="3" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/elements/SimpleText.swift:34">
P2: Inject `AppStore` in `#Preview`; otherwise the preview will crash due to missing `@EnvironmentObject`.</violation>
</file>
<file name="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/SafariViewModal.swift">
<violation number="1" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/SafariViewModal.swift:16">
P1: Force-unwrapping the URL can crash when `inAppSafariUrl` is empty or malformed.</violation>
<violation number="2" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/SafariViewModal.swift:16">
P2: Custom agent: **Flag Security Vulnerabilities**
SafariView is created from a runtime URL string without enforcing HTTPS, allowing insecure HTTP destinations and violating TLS-only security requirements.</violation>
</file>
<file name="ios/nucleum/Nucleum/ContentView.swift">
<violation number="1" location="ios/nucleum/Nucleum/ContentView.swift:14">
P2: `isShowTranscriptionDebugger` is never mutated, so this branch is unreachable and the transcription debugger UI cannot be opened.</violation>
</file>
<file name="ios/memotron/Memotron/WhisperCppWrapper.h">
<violation number="1" location="ios/memotron/Memotron/WhisperCppWrapper.h:12">
P2: This adds a second header for the same `WhisperCppWrapper` interface, duplicating an existing class declaration and creating API drift risk.</violation>
<violation number="2" location="ios/memotron/Memotron/WhisperCppWrapper.h:15">
P2: Mark this initializer as `nullable` because the implementation can return `nil` on failure.</violation>
<violation number="3" location="ios/memotron/Memotron/WhisperCppWrapper.h:21">
P1: The return type should be nullable; this method returns `nil` on multiple error paths.</violation>
</file>
<file name="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Models.swift">
<violation number="1" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Models.swift:71">
P1: Custom agent: **Flag Security Vulnerabilities**
Unvalidated raw URL strings are accepted and propagated to network request execution without enforcing HTTPS/TLS.</violation>
</file>
<file name="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/WebAuthenticationViewForMac.swift">
<violation number="1" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/WebAuthenticationViewForMac.swift:36">
P1: Custom agent: **Flag Security Vulnerabilities**
Authentication flow starts with an unvalidated URL scheme, allowing insecure `http` auth endpoints instead of enforcing HTTPS/TLS.</violation>
</file>
<file name="ios/memotron/Memotron/Info.plist">
<violation number="1" location="ios/memotron/Memotron/Info.plist:21">
P1: Custom agent: **Flag Security Vulnerabilities**
Global ATS disablement (`NSAllowsArbitraryLoads=true`) permits insecure HTTP traffic and weakens transport security app-wide.</violation>
</file>
<file name="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Logger.swift">
<violation number="1" location="ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Logger.swift:93">
P2: Custom agent: **Check System Design and Architectural Patterns**
Logger core path is tightly coupled to network persistence and platform metadata collection, violating separation of concerns.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| isPresented = true | ||
| } | ||
| .sheet(isPresented: $isPresented) { | ||
| WebAuthenticationView(url: URL(string: urlString)!, callbackURLScheme: "tauri") { |
There was a problem hiding this comment.
P1: Force-unwrapping URL(string: urlString) can crash when urlString is empty or invalid (it is currently initialized to an empty string).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/SafariWebview.swift, line 55:
<comment>Force-unwrapping `URL(string: urlString)` can crash when `urlString` is empty or invalid (it is currently initialized to an empty string).</comment>
<file context>
@@ -0,0 +1,106 @@
+ isPresented = true
+ }
+ .sheet(isPresented: $isPresented) {
+ WebAuthenticationView(url: URL(string: urlString)!, callbackURLScheme: "tauri") {
+ callbackURL, error in
+ isPresented = false
</file context>
| struct LocalConfig { | ||
| static let defaultAppName = "Nucleum" | ||
| static let appGroup = "group.io.nucleum" | ||
| static let appDataUrl = URL(string: "") |
There was a problem hiding this comment.
P1: appDataUrl is initialized with an empty URL string, so it is always nil and will crash when force-unwrapped in fetchAppData().
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ios/nucleum/Nucleum/local/LocalConfig.swift, line 13:
<comment>`appDataUrl` is initialized with an empty URL string, so it is always `nil` and will crash when force-unwrapped in `fetchAppData()`.</comment>
<file context>
@@ -0,0 +1,40 @@
+struct LocalConfig {
+ static let defaultAppName = "Nucleum"
+ static let appGroup = "group.io.nucleum"
+ static let appDataUrl = URL(string: "")
+ static let urlScheme = "nucleum"
+
</file context>
| <key>NSAppTransportSecurity</key> | ||
| <dict> | ||
| <key>NSAllowsArbitraryLoads</key> | ||
| <true/> |
There was a problem hiding this comment.
P1: NSAllowsArbitraryLoads is enabled globally, which disables ATS and permits insecure network traffic across the app.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ios/nucleum/Nucleum/Info.plist, line 21:
<comment>`NSAllowsArbitraryLoads` is enabled globally, which disables ATS and permits insecure network traffic across the app.</comment>
<file context>
@@ -0,0 +1,28 @@
+ <key>NSAppTransportSecurity</key>
+ <dict>
+ <key>NSAllowsArbitraryLoads</key>
+ <true/>
+ </dict>
+ <key>UIBackgroundModes</key>
</file context>
| <true/> | |
| <false/> |
| func application(_ application: NSApplication, open urls: [URL]) { | ||
| guard let url = urls.first else { return } | ||
| // Handle the custom URL | ||
| print("URL received in application(_:open:): \(url)") |
There was a problem hiding this comment.
P1: Avoid logging full callback URLs; redact or remove these prints to prevent sensitive data exposure in logs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/MacAppDelegate.swift, line 22:
<comment>Avoid logging full callback URLs; redact or remove these prints to prevent sensitive data exposure in logs.</comment>
<file context>
@@ -0,0 +1,55 @@
+ func application(_ application: NSApplication, open urls: [URL]) {
+ guard let url = urls.first else { return }
+ // Handle the custom URL
+ print("URL received in application(_:open:): \(url)")
+ handleURL(url)
+ }
</file context>
| WebViewTwo( | ||
| urlType: .customProtocolUrl, | ||
| refreshId: refreshId, | ||
| url: URL(string: LocalConfig.appUrl + "/" + appStore.popup.path)!, |
There was a problem hiding this comment.
P1: Avoid force-unwrapping the constructed URL; invalid popup paths can cause a runtime crash when opening the modal.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/Modal.swift, line 31:
<comment>Avoid force-unwrapping the constructed URL; invalid popup paths can cause a runtime crash when opening the modal.</comment>
<file context>
@@ -0,0 +1,59 @@
+ WebViewTwo(
+ urlType: .customProtocolUrl,
+ refreshId: refreshId,
+ url: URL(string: LocalConfig.appUrl + "/" + appStore.popup.path)!,
+ params: [
+ "isSheet": "true", "spath": appStore.popup.path,
</file context>
| ZStack { | ||
| Color.white.opacity(0) | ||
| VStack { | ||
| SafariView(url: URL(string: appStore.inAppSafariUrl)!) |
There was a problem hiding this comment.
P2: Custom agent: Flag Security Vulnerabilities
SafariView is created from a runtime URL string without enforcing HTTPS, allowing insecure HTTP destinations and violating TLS-only security requirements.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/SafariViewModal.swift, line 16:
<comment>SafariView is created from a runtime URL string without enforcing HTTPS, allowing insecure HTTP destinations and violating TLS-only security requirements.</comment>
<file context>
@@ -0,0 +1,25 @@
+ ZStack {
+ Color.white.opacity(0)
+ VStack {
+ SafariView(url: URL(string: appStore.inAppSafariUrl)!)
+ }
+ // .background(appStore.bg)
</file context>
| @State var content: String | ||
| @State var fontSize: CGFloat | ||
| @State var color: Color? | ||
| @EnvironmentObject var appStore: AppStore |
There was a problem hiding this comment.
P2: Custom agent: Check System Design and Architectural Patterns
SimpleText is tightly coupled to global AppStore state, creating improper layering for a reusable UI element.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/elements/SimpleText.swift, line 14:
<comment>`SimpleText` is tightly coupled to global `AppStore` state, creating improper layering for a reusable UI element.</comment>
<file context>
@@ -0,0 +1,35 @@
+ @State var content: String
+ @State var fontSize: CGFloat
+ @State var color: Color?
+ @EnvironmentObject var appStore: AppStore
+ init(_ content: String, color: Color? = nil, fontSize: CGFloat = 16) {
+ self.content = content
</file context>
| @@ -0,0 +1,121 @@ | |||
| // | |||
There was a problem hiding this comment.
P2: Custom agent: Check System Design and Architectural Patterns
Logger core path is tightly coupled to network persistence and platform metadata collection, violating separation of concerns.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Logger.swift, line 93:
<comment>Logger core path is tightly coupled to network persistence and platform metadata collection, violating separation of concerns.</comment>
<file context>
@@ -0,0 +1,121 @@
+ let osVersion = ProcessInfo.processInfo.operatingSystemVersion
+ #if DEBUG
+ if context?.isSaveToServer == true || context == nil {
+ Utils.performSurrealCall(
+ "insert into debugLog {message: $message, timestamp: $timestamp, src: $src, type: $type, app: $app, os: $os, osVersion: $osVersion, device: $device}",
+ [
</file context>
| // } | ||
| // } | ||
| // } | ||
| class AppDelegateTwo: NSObject, NSApplicationDelegate, NSWindowDelegate { |
There was a problem hiding this comment.
P3: Remove the unused draggable window/delegate types; they are not wired into the app and add dead code maintenance overhead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ios/memotron/Memotron for Mac/Memotron_for_MacApp.swift, line 103:
<comment>Remove the unused draggable window/delegate types; they are not wired into the app and add dead code maintenance overhead.</comment>
<file context>
@@ -0,0 +1,153 @@
+// }
+// }
+// }
+class AppDelegateTwo: NSObject, NSApplicationDelegate, NSWindowDelegate {
+ var window: DraggableWindow!
+ var windowController: DraggableWindowController!
</file context>
| // } | ||
| // } | ||
| // } | ||
| class AppDelegateTwo: NSObject, NSApplicationDelegate, NSWindowDelegate { |
There was a problem hiding this comment.
P3: AppDelegateTwo and its custom window flow are dead code because the app is configured to use MacAppDelegate, not this delegate.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ios/nucleum/Nucleum for Mac/Nucleum_for_MacApp.swift, line 103:
<comment>`AppDelegateTwo` and its custom window flow are dead code because the app is configured to use `MacAppDelegate`, not this delegate.</comment>
<file context>
@@ -0,0 +1,153 @@
+// }
+// }
+// }
+class AppDelegateTwo: NSObject, NSApplicationDelegate, NSWindowDelegate {
+ var window: DraggableWindow!
+ var windowController: DraggableWindowController!
</file context>
There was a problem hiding this comment.
Actionable comments posted: 22
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
ios/pointron/Pointron for Mac/Pointron_for_Mac.entitlements (1)
1-17:⚠️ Potential issue | 🔴 CriticalMissing
com.apple.security.application-groupsrequired for widget data sharing.The macOS entitlements file declares no app group, but the app code (Target.swift, TargetWidgetModel.swift, CurrentSession.swift) consistently uses
UserDefaults(suiteName: LocalConfig.appGroup)to share session and color data with the widget extension. The iOS versions (Pointron.entitlements and PointronWidgetExtensionExtension.entitlements) both declaregroup.io.pointron. Add the matching app group to this file to enable widget functionality on macOS:Required addition
<key>com.apple.security.application-groups</key> <array> <string>group.io.pointron</string> </array>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ios/pointron/Pointron` for Mac/Pointron_for_Mac.entitlements around lines 1 - 17, The macOS entitlements file is missing the app group used by the app and widget; add the com.apple.security.application-groups key with an array containing "group.io.pointron" so UserDefaults(suiteName: LocalConfig.appGroup) used in Target.swift, TargetWidgetModel.swift and CurrentSession.swift can access the shared container; ensure the key and array are added alongside the existing com.apple.security.* keys in the entitlements plist.ios/pointron/Pointron.xcodeproj/xcshareddata/xcschemes/Pointron 1.xcscheme (1)
1-79:⚠️ Potential issue | 🟡 MinorLikely accidental duplicate scheme — rename or remove.
The filename
Pointron 1.xcschemefollows Xcode's automatic "Copy N" naming convention, and the contents are nearly identical toPointron dev.xcscheme(only missing thesurrealUrlenv var). Shared schemes appear in every developer's Xcode scheme picker, so an unnamed duplicate is likely to cause confusion about which one to select for builds/CI.Please confirm this file is intentional; if not, delete it. If it is intentional (e.g., a "prod-like" local scheme), give it a descriptive filename such as
Pointron prod.xcschemeorPointron local.xcschemeso its purpose is clear.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ios/pointron/Pointron.xcodeproj/xcshareddata/xcschemes/Pointron` 1.xcscheme around lines 1 - 79, This is likely an accidental duplicate scheme file named "Pointron 1.xcscheme" (references BlueprintIdentifier "22FFA8812AD3E45000103446" / BuildableName "Pointron.app") that duplicates the existing Pointron dev.xcscheme; either delete this file if not intentional, or rename the file to a descriptive shared-scheme name (e.g., Pointron prod.xcscheme or Pointron local.xcscheme) and ensure its contents match the intended purpose (add the missing surrealUrl env var from Pointron dev.xcscheme if needed) so developers/CI aren’t confused by an unnamed duplicate.
♻️ Duplicate comments (1)
ios/nucleum/Nucleum/whisper/WhisperCppWrapper.h (1)
1-7:⚠️ Potential issue | 🟡 MinorHeader comment says
Memotronin the Nucleum target.Lines 3 and 5 still reference
Memotron— misleading for anyone reading this in the Nucleum project. Fix alongside the de-duplication suggestion raised onios/memotron/Memotron/whisper/WhisperCppWrapper.h.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ios/nucleum/Nucleum/whisper/WhisperCppWrapper.h` around lines 1 - 7, Header comment in WhisperCppWrapper.h incorrectly references "Memotron" and should be updated for the Nucleum target and de-duplicated with ios/memotron/Memotron/whisper/WhisperCppWrapper.h; open WhisperCppWrapper.h and replace the file header lines that mention "Memotron" (the top comment block in WhisperCppWrapper.h) with the correct project name "Nucleum" and ensure the header block matches the intended Nucleum wording, and apply the same normalization to the duplicate header in ios/memotron/.../WhisperCppWrapper.h so both files have consistent, target-appropriate comments.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 41b50e64-4154-47e9-9ee6-4ced34cbd02d
⛔ Files ignored due to path filters (134)
ios/memotron/Memotron for Mac/Assets.xcassets/AppIcon.appiconset/16.pngis excluded by!**/*.pngios/memotron/Memotron for Mac/Assets.xcassets/AppIcon.appiconset/Memotron icon 512 px 1x 1.pngis excluded by!**/*.pngios/memotron/Memotron for Mac/Assets.xcassets/AppIcon.appiconset/Memotron icon 512 px 1x.pngis excluded by!**/*.pngios/memotron/Memotron for Mac/Assets.xcassets/AppIcon.appiconset/Memotron icon 512 px 2x.pngis excluded by!**/*.pngios/memotron/Memotron for Mac/Assets.xcassets/AppIcon.appiconset/Memotron icon 512 px 32pt 1.pngis excluded by!**/*.pngios/memotron/Memotron for Mac/Assets.xcassets/AppIcon.appiconset/Memotron icon 512 px 32pt.pngis excluded by!**/*.pngios/memotron/Memotron for Mac/Assets.xcassets/AppIcon.appiconset/Memotron icon 512 px 64pt.pngis excluded by!**/*.pngios/memotron/Memotron for Mac/Assets.xcassets/AppIcon.appiconset/Memotron icon 512 px fourth.pngis excluded by!**/*.pngios/memotron/Memotron for Mac/Assets.xcassets/AppIcon.appiconset/Memotron icon 512 px half copy.pngis excluded by!**/*.pngios/memotron/Memotron for Mac/Assets.xcassets/AppIcon.appiconset/Memotron icon 512 px half.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/100.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/102.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/1024.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/114.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/120.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/128.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/144.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/152.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/16.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/167.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/172.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/180.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/196.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/20.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/216.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/256.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/29.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/32.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/40.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/48.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/50.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/512.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/55.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/57.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/58.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/60.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/64.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/66.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/72.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/76.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/80.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/87.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/88.pngis excluded by!**/*.pngios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/92.pngis excluded by!**/*.pngios/nucleum/Nucleum for Mac/Assets.xcassets/AppIcon.appiconset/16.pngis excluded by!**/*.pngios/nucleum/Nucleum for Mac/Assets.xcassets/AppIcon.appiconset/Memotron icon 512 px 1x 1.pngis excluded by!**/*.pngios/nucleum/Nucleum for Mac/Assets.xcassets/AppIcon.appiconset/Memotron icon 512 px 1x.pngis excluded by!**/*.pngios/nucleum/Nucleum for Mac/Assets.xcassets/AppIcon.appiconset/Memotron icon 512 px 2x.pngis excluded by!**/*.pngios/nucleum/Nucleum for Mac/Assets.xcassets/AppIcon.appiconset/Memotron icon 512 px 32pt 1.pngis excluded by!**/*.pngios/nucleum/Nucleum for Mac/Assets.xcassets/AppIcon.appiconset/Memotron icon 512 px 32pt.pngis excluded by!**/*.pngios/nucleum/Nucleum for Mac/Assets.xcassets/AppIcon.appiconset/Memotron icon 512 px 64pt.pngis excluded by!**/*.pngios/nucleum/Nucleum for Mac/Assets.xcassets/AppIcon.appiconset/Memotron icon 512 px fourth.pngis excluded by!**/*.pngios/nucleum/Nucleum for Mac/Assets.xcassets/AppIcon.appiconset/Memotron icon 512 px half copy.pngis excluded by!**/*.pngios/nucleum/Nucleum for Mac/Assets.xcassets/AppIcon.appiconset/Memotron icon 512 px half.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/100.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/102.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/1024.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/114.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/120.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/128.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/144.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/152.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/16.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/167.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/172.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/180.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/196.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/20.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/216.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/256.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/29.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/32.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/40.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/48.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/50.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/512.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/55.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/57.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/58.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/60.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/64.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/66.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/72.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/76.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/80.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/87.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/88.pngis excluded by!**/*.pngios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/92.pngis excluded by!**/*.pngios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Sounds/dingding.mp3is excluded by!**/*.mp3ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Sounds/ping.wavis excluded by!**/*.wavios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Sounds/upchime.mp3is excluded by!**/*.mp3ios/pointron/Pointron for Mac/Assets.xcassets/AppIcon.appiconset/Pointron mac icon 128 px (1).pngis excluded by!**/*.pngios/pointron/Pointron for Mac/Assets.xcassets/AppIcon.appiconset/Pointron mac icon 128 px.pngis excluded by!**/*.pngios/pointron/Pointron for Mac/Assets.xcassets/AppIcon.appiconset/Pointron mac icon 16 px (1).pngis excluded by!**/*.pngios/pointron/Pointron for Mac/Assets.xcassets/AppIcon.appiconset/Pointron mac icon 16 px.pngis excluded by!**/*.pngios/pointron/Pointron for Mac/Assets.xcassets/AppIcon.appiconset/Pointron mac icon 256 px 1.pngis excluded by!**/*.pngios/pointron/Pointron for Mac/Assets.xcassets/AppIcon.appiconset/Pointron mac icon 256 px.pngis excluded by!**/*.pngios/pointron/Pointron for Mac/Assets.xcassets/AppIcon.appiconset/Pointron mac icon 32 px (1).pngis excluded by!**/*.pngios/pointron/Pointron for Mac/Assets.xcassets/AppIcon.appiconset/Pointron mac icon 32 px.pngis excluded by!**/*.pngios/pointron/Pointron for Mac/Assets.xcassets/AppIcon.appiconset/Pointron mac icon 512 px 1.pngis excluded by!**/*.pngios/pointron/Pointron for Mac/Assets.xcassets/AppIcon.appiconset/Pointron mac icon 512 px.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/100.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/1024.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/114.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/120.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/128.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/144.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/152.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/16.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/167.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/172.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/180.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/196.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/20.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/216.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/256.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/29.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/32.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/40.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/48.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/50.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/512.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/55.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/57.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/58.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/60.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/64.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/66.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/72.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/76.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/80.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/87.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/88.pngis excluded by!**/*.pngios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/92.pngis excluded by!**/*.png
📒 Files selected for processing (124)
.gitignoreclient/components/account/Login.svelteclient/components/calendar/Calendar.svelteclient/components/flux/resourceStores/resource.utils.tsclient/layout/layers/BaseLayer.svelteclient/products/product.config.tsios/memotron/Memotron for Mac/Assets.xcassets/AppIcon.appiconset/Contents.jsonios/memotron/Memotron for Mac/ContentView.swiftios/memotron/Memotron for Mac/Memotron_for_Mac.entitlementsios/memotron/Memotron for Mac/Memotron_for_MacApp.swiftios/memotron/Memotron for Mac/Preview Content/Preview Assets.xcassets/Contents.jsonios/memotron/Memotron-for-Mac-Info.plistios/memotron/Memotron.xcodeproj/project.pbxprojios/memotron/Memotron.xcodeproj/project.xcworkspace/contents.xcworkspacedataios/memotron/Memotron.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plistios/memotron/Memotron.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettingsios/memotron/Memotron/Assets.xcassets/AppIcon.appiconset/Contents.jsonios/memotron/Memotron/ContentView.swiftios/memotron/Memotron/Info.plistios/memotron/Memotron/Memotron.entitlementsios/memotron/Memotron/MemotronApp.swiftios/memotron/Memotron/Preview Content/Preview Assets.xcassets/Contents.jsonios/memotron/Memotron/WhisperCppWrapper.hios/memotron/Memotron/local/LocalConfig.swiftios/memotron/Memotron/whisper/Memotron-Bridging-Header.hios/memotron/Memotron/whisper/README.mdios/memotron/Memotron/whisper/WhisperCppWrapper.hios/memotron/Memotron/whisper/WhisperCppWrapper.mmios/nucleum/Nucleum for Mac/Assets.xcassets/AppIcon.appiconset/Contents.jsonios/nucleum/Nucleum for Mac/ContentView.swiftios/nucleum/Nucleum for Mac/Nucleum_for_Mac.entitlementsios/nucleum/Nucleum for Mac/Nucleum_for_MacApp.swiftios/nucleum/Nucleum for Mac/Preview Content/Preview Assets.xcassets/Contents.jsonios/nucleum/Nucleum-for-Mac-Info.plistios/nucleum/Nucleum.xcodeproj/project.pbxprojios/nucleum/Nucleum.xcodeproj/project.xcworkspace/contents.xcworkspacedataios/nucleum/Nucleum.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plistios/nucleum/Nucleum.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettingsios/nucleum/Nucleum/Assets.xcassets/AppIcon.appiconset/Contents.jsonios/nucleum/Nucleum/ContentView.swiftios/nucleum/Nucleum/Info.plistios/nucleum/Nucleum/Nucleum.entitlementsios/nucleum/Nucleum/NucleumApp.swiftios/nucleum/Nucleum/Preview Content/Preview Assets.xcassets/Contents.jsonios/nucleum/Nucleum/WhisperCppWrapper.hios/nucleum/Nucleum/local/LocalConfig.swiftios/nucleum/Nucleum/whisper/Nucleum-Bridging-Header.hios/nucleum/Nucleum/whisper/README.mdios/nucleum/Nucleum/whisper/WhisperCppWrapper.hios/nucleum/Nucleum/whisper/WhisperCppWrapper.mmios/packages/NucleumNativeKit/Sources/NucleumNativeKit/AIService.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/AIServiceProtocols.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/AppStore.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/CoreDataStack.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Defaults.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Extensions.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Job.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/JobManager.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Logger.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/MacAppDelegate.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/MacContent.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Models.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/README.mdios/packages/NucleumNativeKit/Sources/NucleumNativeKit/StoreManager.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Utils.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/AudioTranscriptionView.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/BaseView.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/BaseViewForMac.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/CameraTestView.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/LoadingOverlay.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/Modal.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/Offline.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/SafariViewModal.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/SafariWebview.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/WebAuthenticationViewForMac.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/elements/SimpleText.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/Views/elements/SimpleWidgetText.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/WebView/CustomWebView.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/WebView/WebViewCommon.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/WebView/WebViewForMac.swiftios/packages/NucleumNativeKit/Sources/NucleumNativeKit/WebView/WebViewTwo.swiftios/pointron/Pointron for Mac/Assets.xcassets/AppIcon.appiconset/Contents.jsonios/pointron/Pointron for Mac/ContentView.swiftios/pointron/Pointron for Mac/Pointron_for_Mac.entitlementsios/pointron/Pointron for Mac/Pointron_for_MacApp.swiftios/pointron/Pointron for Mac/Preview Content/Preview Assets.xcassets/Contents.jsonios/pointron/Pointron-for-Mac-Info.plistios/pointron/Pointron.xcodeproj/project.pbxprojios/pointron/Pointron.xcodeproj/project.xcworkspace/contents.xcworkspacedataios/pointron/Pointron.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plistios/pointron/Pointron.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettingsios/pointron/Pointron.xcodeproj/xcshareddata/xcschemes/Pointron 1.xcschemeios/pointron/Pointron.xcodeproj/xcshareddata/xcschemes/Pointron dev.xcschemeios/pointron/Pointron.xcodeproj/xcshareddata/xcschemes/Pointron for Mac.xcschemeios/pointron/Pointron.xcodeproj/xcshareddata/xcschemes/Pointron-release.xcschemeios/pointron/Pointron.xcodeproj/xcshareddata/xcschemes/Pointron.xcschemeios/pointron/Pointron.xcodeproj/xcshareddata/xcschemes/PointronWidgetExtensionExtension.xcschemeios/pointron/Pointron/Assets.xcassets/AppIcon.appiconset/Contents.jsonios/pointron/Pointron/ContentView.swiftios/pointron/Pointron/Info.plistios/pointron/Pointron/Pointron.entitlementsios/pointron/Pointron/PointronApp.swiftios/pointron/Pointron/Preview Content/Preview Assets.xcassets/Contents.jsonios/pointron/Pointron/local/LocalConfig.swiftios/pointron/Pointron/local/Models/CurrentSession.swiftios/pointron/Pointron/local/Models/SurrealModels.swiftios/pointron/Pointron/local/Models/Target.swiftios/pointron/Pointron/local/Models/TargetWidgetModel.swiftios/pointron/PointronWidgetExtension/AppIntent.swiftios/pointron/PointronWidgetExtension/Assets.xcassets/AccentColor.colorset/Contents.jsonios/pointron/PointronWidgetExtension/Assets.xcassets/AppIcon.appiconset/Contents.jsonios/pointron/PointronWidgetExtension/Assets.xcassets/Contents.jsonios/pointron/PointronWidgetExtension/Assets.xcassets/WidgetBackground.colorset/Contents.jsonios/pointron/PointronWidgetExtension/Info.plistios/pointron/PointronWidgetExtension/PointronWidgetExtension.swiftios/pointron/PointronWidgetExtension/PointronWidgetExtensionBundle.swiftios/pointron/PointronWidgetExtension/PointronWidgetExtensionLiveActivity.swiftios/pointron/PointronWidgetExtension/Widgets/CurrentSessionWidget.swiftios/pointron/PointronWidgetExtension/Widgets/TargetsWidget.swiftios/pointron/PointronWidgetExtensionExtension.entitlementsios/pointron/sheet/index.htmlpackage.jsontools/ios/build-web-bundle.mjstools/ios/setup-whisper.mjs
| <key>com.apple.security.device.audio-input</key> | ||
| <true/> | ||
| <key>com.apple.security.device.camera</key> | ||
| <true/> | ||
| <key>com.apple.security.files.downloads.read-write</key> | ||
| <true/> | ||
| <key>com.apple.security.files.user-selected.read-only</key> | ||
| <true/> | ||
| <key>com.apple.security.network.client</key> | ||
| <true/> | ||
| <key>com.apple.security.personal-information.location</key> | ||
| <true/> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify usage description keys are present in the Memotron mac Info.plist.
fd -t f -i 'Memotron-for-Mac-Info.plist' -x sh -c 'echo "--- {} ---"; cat {}'
fd -t f -i 'Info.plist' -E 'Tests' -E 'UITests' -x grep -lE 'Memotron|memotron' {} \; 2>/dev/null | xargs -I{} sh -c 'echo "=== {} ==="; grep -E "NSMicrophoneUsageDescription|NSCameraUsageDescription|NSLocation.*UsageDescription" {} || echo "(none)"'Repository: 21nCo/nucleum
Length of output: 687
Add required NSUsageDescription keys to the macOS Info.plist for declared privacy entitlements.
The entitlements file declares device.audio-input, device.camera, and personal-information.location, but the corresponding usage-description strings are missing from the Info.plist files (NSMicrophoneUsageDescription, NSCameraUsageDescription, NSLocationUsageDescription). Without these keys, the app will crash on macOS when the first privacy prompt is triggered.
Add the missing keys to ios/memotron/Memotron-for-Mac-Info.plist (or the actively used Info.plist for the macOS target):
NSMicrophoneUsageDescriptionNSCameraUsageDescriptionNSLocationUsageDescriptionorNSLocationWhenInUseUsageDescription
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ios/memotron/Memotron` for Mac/Memotron_for_Mac.entitlements around lines 11
- 22, The entitlements declare com.apple.security.device.audio-input,
com.apple.security.device.camera and
com.apple.security.personal-information.location but the macOS Info.plist is
missing the corresponding usage-description keys; open the Info.plist for the
macOS target and add NSMicrophoneUsageDescription, NSCameraUsageDescription, and
either NSLocationUsageDescription or NSLocationWhenInUseUsageDescription with
meaningful non-empty user-facing strings describing why the app needs
microphone, camera and location access so the system prompts will not crash the
app.
| <key>com.apple.security.device.audio-input</key> | ||
| <true/> | ||
| <key>com.apple.security.device.camera</key> | ||
| <true/> | ||
| <key>com.apple.security.files.downloads.read-write</key> | ||
| <true/> | ||
| <key>com.apple.security.files.user-selected.read-only</key> | ||
| <true/> | ||
| <key>com.apple.security.network.client</key> | ||
| <true/> | ||
| <key>com.apple.security.personal-information.location</key> | ||
| <true/> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
fd -t f -i 'Nucleum-for-Mac-Info.plist' -x sh -c 'echo "--- {} ---"; cat {}'
fd -t f -i 'Info.plist' -x grep -lE 'Nucleum|nucleum' {} \; 2>/dev/null | xargs -I{} sh -c 'echo "=== {} ==="; grep -E "NSMicrophoneUsageDescription|NSCameraUsageDescription|NSLocation.*UsageDescription" {} || echo "(none)"'Repository: 21nCo/nucleum
Length of output: 679
Add missing NSMicrophoneUsageDescription, NSCameraUsageDescription, and NSLocation*UsageDescription keys to macOS Info.plist.
The Nucleum for Mac entitlements file declares audio-input, camera, and location access permissions, but the corresponding usage descriptions are absent from Nucleum-for-Mac-Info.plist. Without these keys, the app will crash when attempting to access the microphone, camera, or location data. Add the required entitlement descriptions to the Info.plist file.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ios/nucleum/Nucleum` for Mac/Nucleum_for_Mac.entitlements around lines 11 -
22, Your entitlements declare com.apple.security.device.audio-input,
com.apple.security.device.camera, and
com.apple.security.personal-information.location but the Info.plist is missing
the corresponding usage description keys; add NSMicrophoneUsageDescription and
NSCameraUsageDescription and add the appropriate location usage keys (e.g.,
NSLocationWhenInUseUsageDescription and, if you request always access,
NSLocationAlwaysAndWhenInUseUsageDescription) to Nucleum-for-Mac-Info.plist with
clear user-facing strings describing why the app needs microphone, camera, and
location access so macOS won't crash when those features are requested.
| init() { | ||
| // Initialize the AI service on the shared AppStore instance | ||
| AppStore.shared.setAIServiceProvider(AIService.shared) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect both singletons' initializers to confirm they are lightweight and main-thread-safe.
fd -t f -e swift -HI 'AppStore.swift'
fd -t f -e swift -HI 'AIService.swift'
ast-grep --pattern $'class AppStore: $_ {
$$$
}'
ast-grep --pattern $'class AIService: $_ {
$$$
}'Repository: 21nCo/nucleum
Length of output: 198
🏁 Script executed:
cat -n ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/AppStore.swiftRepository: 21nCo/nucleum
Length of output: 50369
🏁 Script executed:
cat -n ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/AIService.swiftRepository: 21nCo/nucleum
Length of output: 50370
Avoid heavy initialization work in AIService.shared during app startup.
AIService.init() calls downloadModelIfNeeded(), which initiates a network download of the Whisper model from HuggingFace if not cached (ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/AIService.swift, lines 190-233). This download initiation happens on the main thread during app launch, causing noticeable startup delays on slower connections.
Defer model downloads to lazy initialization only when transcription is actually requested, rather than eagerly during AIService.shared creation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ios/nucleum/Nucleum` for Mac/Nucleum_for_MacApp.swift around lines 14 - 17,
The app is creating AIService.shared in App startup which triggers
AIService.init() and calls downloadModelIfNeeded(), causing network downloads on
the main thread; change AIService to defer heavy work by making
downloadModelIfNeeded() lazy and only invoked when transcription is requested.
Modify AIService so its init() does not start downloads (remove or guard calls
to downloadModelIfNeeded()), add a lazy initializer or explicit method (e.g.,
prepareModelIfNeeded or ensureModelAvailable) that runs downloadModelIfNeeded()
off the background queue, and update callers (transcription entry points) to
call that preparation method before starting transcription; keep
AppStore.shared.setAIServiceProvider(AIService.shared) as a lightweight
registration.
| } else if (bits_per_sample == 24) { | ||
| // Handle 24-bit audio (3 bytes per sample) | ||
| for (int i = 0; i < n_samples; i++) { | ||
| float sum = 0.0f; | ||
| for (int j = 0; j < num_channels; j++) { | ||
| // Read 3 bytes and convert to a 24-bit integer | ||
| int sample_idx = dataPos + (i * num_channels + j) * 3; | ||
| int32_t sample = (bytes[sample_idx] << 8) | (bytes[sample_idx + 1] << 16) | (bytes[sample_idx + 2] << 24); | ||
| // Convert to float and normalize | ||
| sum += sample / 8388608.0f; // 2^23 | ||
| } | ||
| pcmf32[i] = sum / num_channels; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In little-endian 24-bit signed PCM WAV, what is the correct normalization factor to map samples to the range [-1.0, 1.0]?
💡 Result:
The correct normalization factor for 24-bit signed PCM WAV samples to map to the range [-1.0, 1.0] is 2^23, or 8388608. Divide the integer sample value by 8388608.0 to get a normalized float in approximately [-1.0, 0.9999999]. This treats the minimum value (-8388608) as -1.0 and the maximum (8388607) as nearly +1.0, consistent with two's complement representation and common audio library practices like libsndfile (which normalizes all PCM widths to [-1.0, 1.0]) and wavio (explicitly / 223).
Citations:
- 1: Wav samples are not read correctly 24bit/48khz WarrenWeckesser/wavio#11
- 2: https://www.kvraudio.com/forum/viewtopic.php?t=63602
- 3: https://libsndfile.github.io/libsndfile/api.html
- 4: https://stackoverflow.com/questions/32087782/how-to-normalize-wave-read-in-python
- 5: https://github.com/erikd/libsndfile/blob/master/src/sndfile.c
🏁 Script executed:
#!/bin/bash
# 1. Find and examine the WhisperCppWrapper.mm file in nucleum
find . -name "WhisperCppWrapper.mm" -type fRepository: 21nCo/nucleum
Length of output: 161
🏁 Script executed:
#!/bin/bash
# 2. Once located, check lines 194-206 of nucleum's WhisperCppWrapper.mm
cat -n ios/nucleum/Nucleum/whisper/WhisperCppWrapper.mm | sed -n '190,210p'Repository: 21nCo/nucleum
Length of output: 1183
🏁 Script executed:
#!/bin/bash
# 3. Check if identical code exists in memotron
cat -n ios/memotron/Memotron/whisper/WhisperCppWrapper.mm | sed -n '190,210p'Repository: 21nCo/nucleum
Length of output: 1183
🏁 Script executed:
#!/bin/bash
# 4. Search for bounds checks around dataPos and dataSize in nucleum file
rg -n "dataPos|dataSize|bounds|OOB" ios/nucleum/Nucleum/whisper/WhisperCppWrapper.mmRepository: 21nCo/nucleum
Length of output: 829
🏁 Script executed:
#!/bin/bash
# 5. Check for division-by-zero guards related to num_channels or bits_per_sample
rg -n -B2 -A2 "num_channels|bits_per_sample" ios/nucleum/Nucleum/whisper/WhisperCppWrapper.mm | head -80Repository: 21nCo/nucleum
Length of output: 3783
24-bit PCM samples are scaled 256× too large, degrading Whisper recognition.
(bytes[sample_idx] << 8) | (bytes[sample_idx + 1] << 16) | (bytes[sample_idx + 2] << 24) places the little-endian 24-bit value in bits [8–31] instead of [0–22], multiplying it by 256 before normalization. Dividing by 8388608 (2^23) then yields samples in ~[-256, 256] instead of [-1, 1]. Whisper's mel-spectrogram frontend will clip/distort these out-of-range values, severely degrading recognition quality.
Additionally, shifting a uint8_t left by 24 is undefined behavior when the top bit is set (the value is integer-promoted to int before shifting into the sign bit). Cast to uint32_t first.
The identical bug exists in ios/memotron/Memotron/whisper/WhisperCppWrapper.mm at the same lines and requires the same fix.
🐛 Proposed fix
} else if (bits_per_sample == 24) {
// Handle 24-bit audio (3 bytes per sample)
for (int i = 0; i < n_samples; i++) {
float sum = 0.0f;
for (int j = 0; j < num_channels; j++) {
// Read 3 bytes and convert to a 24-bit integer
int sample_idx = dataPos + (i * num_channels + j) * 3;
- int32_t sample = (bytes[sample_idx] << 8) | (bytes[sample_idx + 1] << 16) | (bytes[sample_idx + 2] << 24);
- // Convert to float and normalize
- sum += sample / 8388608.0f; // 2^23
+ // Little-endian 24-bit signed PCM → place in upper 24 bits of int32
+ // for proper sign extension, then arithmetic-shift back by 8.
+ int32_t packed = (int32_t)((uint32_t)bytes[sample_idx] << 8
+ | (uint32_t)bytes[sample_idx + 1] << 16
+ | (uint32_t)bytes[sample_idx + 2] << 24);
+ int32_t sample = packed >> 8; // arithmetic shift, preserves sign
+ sum += sample / 8388608.0f; // 2^23
}
pcmf32[i] = sum / num_channels;
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } else if (bits_per_sample == 24) { | |
| // Handle 24-bit audio (3 bytes per sample) | |
| for (int i = 0; i < n_samples; i++) { | |
| float sum = 0.0f; | |
| for (int j = 0; j < num_channels; j++) { | |
| // Read 3 bytes and convert to a 24-bit integer | |
| int sample_idx = dataPos + (i * num_channels + j) * 3; | |
| int32_t sample = (bytes[sample_idx] << 8) | (bytes[sample_idx + 1] << 16) | (bytes[sample_idx + 2] << 24); | |
| // Convert to float and normalize | |
| sum += sample / 8388608.0f; // 2^23 | |
| } | |
| pcmf32[i] = sum / num_channels; | |
| } | |
| } else if (bits_per_sample == 24) { | |
| // Handle 24-bit audio (3 bytes per sample) | |
| for (int i = 0; i < n_samples; i++) { | |
| float sum = 0.0f; | |
| for (int j = 0; j < num_channels; j++) { | |
| // Read 3 bytes and convert to a 24-bit integer | |
| int sample_idx = dataPos + (i * num_channels + j) * 3; | |
| // Little-endian 24-bit signed PCM → place in upper 24 bits of int32 | |
| // for proper sign extension, then arithmetic-shift back by 8. | |
| int32_t packed = (int32_t)((uint32_t)bytes[sample_idx] << 8 | |
| | (uint32_t)bytes[sample_idx + 1] << 16 | |
| | (uint32_t)bytes[sample_idx + 2] << 24); | |
| int32_t sample = packed >> 8; // arithmetic shift, preserves sign | |
| sum += sample / 8388608.0f; // 2^23 | |
| } | |
| pcmf32[i] = sum / num_channels; | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ios/nucleum/Nucleum/whisper/WhisperCppWrapper.mm` around lines 194 - 206, The
24-bit PCM handling in the bits_per_sample == 24 branch incorrectly assembles
bytes (shifting into bits 8–31) and risks UB by shifting uint8_t; fix by
assembling the little-endian 24-bit sample as a uint32_t using
(uint32_t)bytes[sample_idx] | ((uint32_t)bytes[sample_idx+1] << 8) |
((uint32_t)bytes[sample_idx+2] << 16), then sign-extend the 24-bit value to a
signed 32-bit (e.g., if (val & 0x800000) val |= 0xFF000000), convert to float
and normalize by dividing by 8388608.0f before mixing into pcmf32; apply the
same change in the corresponding
ios/memotron/Memotron/whisper/WhisperCppWrapper.mm file.
| } else if value.keys.contains("purchase") { | ||
| Log.info("purchase request: \(value)") | ||
| if let purchaseData = value["purchase"] as? [String: Any], | ||
| let productId = purchaseData["productId"] as? String | ||
| { | ||
| // Optional parameters | ||
| let offerToken = purchaseData["offerToken"] as? String | ||
| let nonce = purchaseData["nonce"] as? String | ||
| handlePurchase(productId: productId, nonce: nonce!, offerToken: offerToken) | ||
| } else if let purchaseString = value["purchase"] as? String { | ||
| // Try to parse the purchase data as a JSON string | ||
| do { | ||
| if let jsonData = purchaseString.data(using: .utf8), | ||
| let purchaseDict = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any], | ||
| let productId = purchaseDict["productId"] as? String | ||
| { | ||
| // Optional parameters | ||
| let offerToken = purchaseDict["offerToken"] as? String | ||
| let nonce = purchaseDict["nonce"] as? String | ||
| handlePurchase(productId: productId, nonce: nonce!, offerToken: offerToken) | ||
| } else { | ||
| Log.error( | ||
| message: "Invalid purchase request: missing or invalid productId in JSON string") | ||
| self.sendMessageToApp(message: [ | ||
| "type": "PURCHASE_ERROR", | ||
| "error": "Invalid purchase request: missing or invalid productId in JSON string", | ||
| ]) | ||
| } | ||
| } catch { | ||
| Log.error(message: "Failed to parse purchase JSON string: \(error.localizedDescription)") | ||
| self.sendMessageToApp(message: [ | ||
| "type": "PURCHASE_ERROR", | ||
| "error": "Failed to parse purchase JSON string: \(error.localizedDescription)", | ||
| ]) | ||
| } | ||
| } else { | ||
| Log.error(message: "Invalid purchase request: missing productId") | ||
| self.sendMessageToApp(message: [ | ||
| "type": "PURCHASE_ERROR", "error": "Invalid purchase request: missing productId", | ||
| ]) | ||
| } |
There was a problem hiding this comment.
Force-unwrap of nonce will crash on any purchase request missing a nonce.
In both branches (Line 591 and Line 602), nonce is obtained as an optional (as? String) and then force-unwrapped into handlePurchase(productId:nonce:offerToken:). A web payload that provides productId but omits nonce — which is very possible for restore/non-nonce flows — will crash the app. offerToken is correctly treated as optional; nonce should be too, or the code should validate+reject the request before calling.
🐛 Proposed fix
if let purchaseData = value["purchase"] as? [String: Any],
let productId = purchaseData["productId"] as? String
{
- // Optional parameters
let offerToken = purchaseData["offerToken"] as? String
- let nonce = purchaseData["nonce"] as? String
- handlePurchase(productId: productId, nonce: nonce!, offerToken: offerToken)
+ guard let nonce = purchaseData["nonce"] as? String else {
+ Log.error(message: "Missing nonce for purchase")
+ self.sendMessageToApp(message: [
+ "type": "PURCHASE_ERROR",
+ "error": "Missing nonce",
+ ])
+ return
+ }
+ handlePurchase(productId: productId, nonce: nonce, offerToken: offerToken)
} else if let purchaseString = value["purchase"] as? String {
...
- let nonce = purchaseDict["nonce"] as? String
- handlePurchase(productId: productId, nonce: nonce!, offerToken: offerToken)
+ guard let nonce = purchaseDict["nonce"] as? String else { /* send error & return */ }
+ handlePurchase(productId: productId, nonce: nonce, offerToken: offerToken)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ios/packages/NucleumNativeKit/Sources/NucleumNativeKit/AppStore.swift` around
lines 583 - 623, The code force-unwraps nonce before calling
handlePurchase(productId:nonce:offerToken:) which will crash when nonce is
absent; update both branches that call handlePurchase (the dictionary branch
handling purchaseData and the JSON string branch handling purchaseDict) to treat
nonce as an optional: either validate and return a PURCHASE_ERROR if nonce is
required, or pass nonce (which may be nil) into handlePurchase without using !,
mirroring how offerToken is handled, and adjust any handlePurchase
signature/logic if it must accept an optional nonce.
| struct Targets: Decodable { | ||
| var isSessionRunning: Bool; | ||
| var goalName: String?; | ||
| var color: String?; | ||
| var start: Date?; | ||
| var end: Date?; | ||
| var todayFocus: Float?; | ||
| var isLoggedIn: Bool?; | ||
| var isFocusing: Bool; | ||
| static let defaultEntry = CurrentSessionEntry(date: Date(), session: CurrentSession(isSessionRunning: false, isFocusing: false),colors: resolveColors()); | ||
|
|
||
| static func resolveColors() -> ColorSchemeColors { | ||
| let sharedDefaults = UserDefaults(suiteName: LocalConfig.appGroup); | ||
| var colors: ColorSchemeColors = defaultScheme.colors; | ||
| if let schemeString = sharedDefaults?.string(forKey: "scheme") { | ||
| let jsonData = Data(schemeString.utf8) | ||
| let decoder = JSONDecoder() | ||
| do { | ||
| let scheme = try decoder.decode(ColorScheme.self, from: jsonData) | ||
| colors = scheme.colors; | ||
| } catch { | ||
| // logs.append("error in decoding scheme: \(error.localizedDescription), schemeString: \(schemeString)"); | ||
| } | ||
| } | ||
| return colors; | ||
| } | ||
|
|
||
| static func refreshSessionInformation(completion:@escaping (CurrentSession?, Error?) -> Void) { | ||
| let sharedDefaults = UserDefaults(suiteName: LocalConfig.appGroup); | ||
| var logs: [String] = []; | ||
| do { | ||
| var userId: String? = nil; | ||
| if let user = sharedDefaults?.string(forKey: "userId") { | ||
| userId = user; | ||
| } else{ | ||
| logs.append("userId not found. User not logged in") | ||
| } | ||
| let dateFormatter = DateFormatter() | ||
| dateFormatter.locale = Locale(identifier: "en_US_POSIX") | ||
| dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" | ||
| dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) | ||
| let decoder = JSONDecoder() | ||
| decoder.dateDecodingStrategy = .formatted(dateFormatter) | ||
| let elapsed = resolveBackgroundElapsed(); | ||
| logs.append("background elapsed: \(elapsed)") | ||
| if(elapsed > 5 * 60){ | ||
| if let sessionDataString = sharedDefaults?.string(forKey: "sessionData") { | ||
| let jsonData = Data(sessionDataString.utf8) | ||
| do { | ||
| var sessionData = try decoder.decode(CurrentSession.self, from: jsonData) | ||
| sessionData.todayFocus = (sessionData.todayFocus ?? 0) / (60 * 60) | ||
| completion(sessionData, nil); | ||
| saveLogs(logs) | ||
| return; | ||
| } catch { | ||
| logs.append("error in decoding sessiondata: \(error.localizedDescription), sessionDataString: \(sessionDataString)") | ||
| } | ||
| } | ||
| } | ||
| var request = URLRequest(url: URL(string: "https://\(LocalConfig.surrealUrl)")!); | ||
| request.httpMethod = "POST"; | ||
| var surrealToken: String? = nil; | ||
| if let token = sharedDefaults?.string(forKey: "surrealToken") { | ||
| surrealToken = token; | ||
| } | ||
| if(surrealToken == nil || userId == nil){ | ||
| logs.append("surrealToken and userId not found") | ||
| completion(nil, nil); | ||
| saveLogs(logs); | ||
| return; | ||
| } | ||
| request.addValue("Bearer \(surrealToken!)", forHTTPHeaderField: "Authorization") | ||
| request.httpBody = "USE database \(userId!); return fn::pointron::iOS::currentSessionWidget();".data(using: .utf8) | ||
| request.addValue("text/plain", forHTTPHeaderField: "Content-Type") | ||
| request.addValue("application/json", forHTTPHeaderField: "Accept") | ||
| // if let headers = request.allHTTPHeaderFields { | ||
| // for (headerField, value) in headers { | ||
| // print("\(headerField): \(value)") | ||
| // } | ||
| // } else { | ||
| // print("No headers set for this request.") | ||
| // } | ||
| let task = URLSession.shared.dataTask(with: request) { (data, response, error) in | ||
| if error != nil { | ||
| print("URL session error: \(error)") | ||
| logs.append("error in data task: \(error)"); | ||
| saveLogs(logs); | ||
| completion(nil, error); | ||
| return; | ||
| } else { | ||
| // Print status code | ||
| if let httpResponse = response as? HTTPURLResponse { | ||
| logs.append("Status Code: \(httpResponse.statusCode)") | ||
| } | ||
| // Print response body | ||
| if let data = data { | ||
| let responseBody = String(data: data, encoding: .utf8) ?? "Couldn't decode data" | ||
| logs.append("Response Body: \(responseBody)") | ||
| } | ||
| if let unwrappedData = data { | ||
| do { | ||
| logs.append("JSON unwrappedData: \(unwrappedData)"); | ||
| sharedDefaults?.set(logs, forKey: "widgetLogs") | ||
| let response = try decoder.decode([SurrealResult].self, from: unwrappedData) | ||
| if let currentSessions = response[1].result { | ||
| logs.append("parsed currentsessions: \(currentSessions.count)"); | ||
| saveLogs(logs); | ||
| completion(currentSessions[0], nil); | ||
| return; | ||
| } | ||
| } catch { | ||
| logs.append("parse error in Json: \(error)"); | ||
| saveLogs(logs); | ||
| completion(nil, error); | ||
| return; | ||
| } | ||
| } else { | ||
| logs.append("Unable to unwrap data: \(error)"); | ||
| saveLogs(logs) | ||
| completion(nil, error); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| task.resume() | ||
| } | ||
| catch { | ||
| completion(nil, error) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find callers/definitions of the refresh APIs to determine which are live.
rg -nP -C2 '\bTargets\.(refreshSessionInformation|defaultEntry|resolveColors)\b'
rg -nP -C2 '\bCurrentSession\.(refreshSessionInformation|defaultEntry)\b'
fd -e swift CurrentSessionRepository: 21nCo/nucleum
Length of output: 2275
🏁 Script executed:
cat -n ios/pointron/Pointron/local/Models/CurrentSession.swiftRepository: 21nCo/nucleum
Length of output: 9123
🏁 Script executed:
# Search for any usage of Targets struct
rg -n 'Targets\s*[({]|Targets\.' --type swiftRepository: 21nCo/nucleum
Length of output: 39
🏁 Script executed:
# Search for imports of Target.swift or references to it
rg -n 'import.*Target|from.*Target' --type swiftRepository: 21nCo/nucleum
Length of output: 39
🏁 Script executed:
# Find TargetWidgetModel.swift and check the duplication claim
fd -e swift TargetWidgetModelRepository: 21nCo/nucleum
Length of output: 116
🏁 Script executed:
cat -n ios/pointron/Pointron/local/Models/TargetWidgetModel.swiftRepository: 21nCo/nucleum
Length of output: 6633
🏁 Script executed:
# Search for any actual usage of the Targets struct
rg -n 'Targets\(' --type swiftRepository: 21nCo/nucleum
Length of output: 39
🏁 Script executed:
# Search for imports of Target.swift
rg -n 'Target\.swift|from "Target"|import.*Target' --type swiftRepository: 21nCo/nucleum
Length of output: 202
🏁 Script executed:
# Final confirmation: search for any actual instantiation or use of Targets struct
rg -n '\bTargets\b' --type swift -C 2Repository: 21nCo/nucleum
Length of output: 2323
Delete the Targets struct—it's unused dead code that duplicates CurrentSession.
The Targets struct in this file is not called anywhere in the codebase. The active widget code uses CurrentSession.refreshSessionInformation() and CurrentSession.defaultEntry instead. Meanwhile, Targets has identical fields and static methods to CurrentSession (same isSessionRunning, goalName, color, start, end, todayFocus, isFocusing, defaultEntry, refreshSessionInformation(), and resolveColors()). This entire struct should be removed; the separate TargetWidgetModel is the correct model for the targets widget and is not a duplicate of this code.
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis
[warning] 99-99: Remove this redundant return.
[warning] 56-56: Merge this if statement with the nested one.
🪛 SwiftLint (0.63.2)
[Warning] 12-12: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 13-13: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 14-14: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 15-15: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 16-16: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 17-17: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 18-18: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 19-19: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 20-20: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 23-23: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 24-24: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 30-30: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 35-35: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 39-39: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 40-40: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 42-42: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 44-44: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 54-54: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 62-62: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 64-64: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 70-70: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 71-71: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 72-72: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 74-74: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 78-78: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 79-79: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 80-80: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 96-96: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 97-97: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 98-98: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 99-99: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 112-112: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 116-116: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 117-117: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 118-118: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 119-119: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 122-122: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 123-123: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 124-124: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 125-125: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 128-128: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 130-130: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 131-131: Lines should not have trailing semicolons
(trailing_semicolon)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ios/pointron/Pointron/local/Models/Target.swift` around lines 11 - 141,
Remove the unused duplicate Targets struct entirely (the struct named Targets
plus its static methods resolveColors() and refreshSessionInformation() and the
static let defaultEntry) since CurrentSession already provides
CurrentSession.refreshSessionInformation() and CurrentSession.defaultEntry;
delete the Targets declaration and its helper code, run a quick project-wide
search to ensure no remaining references to Targets remain, and keep
TargetWidgetModel as the widget model.
| if let unwrappedData = data { | ||
| do { | ||
| logs.append("JSON unwrappedData: \(unwrappedData)"); | ||
| sharedDefaults?.set(logs, forKey: "widgetLogs") | ||
| let response = try decoder.decode([SurrealResult].self, from: unwrappedData) | ||
| if let currentSessions = response[1].result { | ||
| logs.append("parsed currentsessions: \(currentSessions.count)"); | ||
| saveLogs(logs); | ||
| completion(currentSessions[0], nil); | ||
| return; | ||
| } | ||
| } catch { | ||
| logs.append("parse error in Json: \(error)"); | ||
| saveLogs(logs); | ||
| completion(nil, error); | ||
| return; | ||
| } | ||
| } else { | ||
| logs.append("Unable to unwrap data: \(error)"); | ||
| saveLogs(logs) | ||
| completion(nil, error); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Same critical bug as in TargetWidgetModel.swift: unbounded index access and a path where completion is never invoked.
response[1].result (line 115), currentSessions[0] (line 118), and the missing else branch on if let currentSessions = response[1].result reproduce the exact issue flagged in TargetWidgetModel.swift. Please apply the same guarded fix here (bounds-check response, use currentSessions.first, and call completion(nil, nil) in the fall-through).
🧰 Tools
🪛 SwiftLint (0.63.2)
[Warning] 112-112: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 116-116: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 117-117: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 118-118: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 119-119: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 122-122: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 123-123: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 124-124: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 125-125: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 128-128: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 130-130: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 131-131: Lines should not have trailing semicolons
(trailing_semicolon)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ios/pointron/Pointron/local/Models/Target.swift` around lines 110 - 132, The
parse block currently accesses response[1] and currentSessions[0] unsafely and
can skip calling completion; fix by first verifying response.count > 1 before
using response[1], use currentSessions.first (or check currentSessions.isEmpty)
instead of currentSessions[0], and add an explicit else/fall-through that calls
completion(nil, nil) when no sessions are found; also ensure the unwrappedData
else branch does not reference an undefined error variable and calls
completion(nil, nil) or passes a real error to completion consistently; update
the logic around decoder.decode, response[1].result, saveLogs, and completion to
reflect these guarded checks.
| if let unwrappedData = data { | ||
| do { | ||
| logs.append("JSON unwrappedData: \(unwrappedData)"); | ||
| sharedDefaults?.set(logs, forKey: "widgetLogs") | ||
| let response = try decoder.decode([TargetsSurrealResult].self, from: unwrappedData) | ||
| if let currentSessions = response[1].result { | ||
| logs.append("parsed currentsessions: \(currentSessions.count)"); | ||
| saveLogs(logs); | ||
| completion(currentSessions[0], nil); | ||
| return; | ||
| } | ||
| } catch { | ||
| logs.append("parse error in Json: \(error)"); | ||
| saveLogs(logs); | ||
| completion(nil, error); | ||
| return; | ||
| } | ||
| } else { | ||
| logs.append("Unable to unwrap data: \(error)"); | ||
| saveLogs(logs) | ||
| completion(nil, error); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Critical: unbounded index access and a code path that never calls completion.
Three related bugs in the success branch:
response[1](line 99) crashes if the server returns fewer than two results.currentSessions[0](line 103) crashes if.resultis an empty array.- If
response[1].resultisnil, theif letis skipped and control falls through to the end of the closure withcompletionnever called — the widget timeline provider will hang until it's killed.
Same issues exist in the sibling Target.swift — fix them in both.
🐛 Suggested fix
- let response = try decoder.decode([TargetsSurrealResult].self, from: unwrappedData)
- if let currentSessions = response[1].result {
- logs.append("parsed currentsessions: \(currentSessions.count)");
- saveLogs(logs);
- completion(currentSessions[0], nil);
- return;
- }
+ let response = try decoder.decode([TargetsSurrealResult].self, from: unwrappedData)
+ guard response.count > 1,
+ let currentSessions = response[1].result,
+ let first = currentSessions.first else {
+ logs.append("Unexpected SurrealDB response shape; results missing")
+ saveLogs(logs)
+ completion(nil, nil)
+ return
+ }
+ logs.append("parsed currentsessions: \(currentSessions.count)")
+ saveLogs(logs)
+ completion(first, nil)
+ return🧰 Tools
🪛 SwiftLint (0.63.2)
[Warning] 97-97: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 101-101: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 102-102: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 103-103: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 104-104: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 107-107: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 108-108: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 109-109: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 110-110: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 113-113: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 115-115: Lines should not have trailing semicolons
(trailing_semicolon)
[Warning] 116-116: Lines should not have trailing semicolons
(trailing_semicolon)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ios/pointron/Pointron/local/Models/TargetWidgetModel.swift` around lines 95 -
117, The success-path unwrapping uses unsafe indexing (response[1]) and assumes
non-empty result (currentSessions[0]) and can skip calling completion; fix by
validating array bounds and optionals: check response.count > 1 before accessing
response[1], safely unwrap response[1].result into currentSessions and ensure
currentSessions.count > 0 before using [0], and if any of those validations fail
call completion(nil, some appropriate Error) and saveLogs; apply the same
defensive checks and guaranteed completion invocation to the sibling
Target.swift, referencing the decoder.decode([TargetsSurrealResult].self,
from:), response, currentSessions, logs, saveLogs and completion symbols.
| import SwiftUI | ||
|
|
||
| @main | ||
| struct PointronApp: App { | ||
| init() { | ||
| // Request permission for notifications | ||
| UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { | ||
| success, error in | ||
| if success { | ||
| print("Notifications permission granted") | ||
| } else if let error = error { | ||
| print(error.localizedDescription) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Does Swift UserNotifications module need explicit import to use UNUserNotificationCenter, or is it re-exported by SwiftUI?
💡 Result:
No, the Swift UserNotifications module requires an explicit import to use UNUserNotificationCenter. It is not re-exported by SwiftUI. All authoritative sources, including Apple documentation and SwiftUI tutorials, show that UserNotifications must be explicitly imported even in SwiftUI views. For example: - Apple docs for UNUserNotificationCenter do not mention re-exporting. - SwiftUI examples consistently use import SwiftUI followed by import UserNotifications before using UNUserNotificationCenter.current [2,3,5,9,21,28]. - No sources indicate SwiftUI re-exports UserNotifications; it re-exports Combine via @_exported, but not UserNotifications [23]. Without import UserNotifications, attempting to use UNUserNotificationCenter results in "Use of undeclared type" errors [2].
Citations:
- 1: https://developer.apple.com/documentation/usernotifications/unusernotificationcenter
- 2: https://stackoverflow.com/questions/45230784/use-of-undeclared-type-unusernotificationcenter
- 3: https://swiftinsg.org/learn/notifications
- 4: https://www.hackingwithswift.com/read/21/2/scheduling-notifications-unusernotificationcenter-and-unnotificationrequest
- 5: https://stackoverflow.com/questions/74567769/implement-swiftui-notifications
- 6: https://developer.apple.com/documentation/usernotifications
- 7: https://developer.apple.com/documentation/UserNotifications/UNUserNotificationCenter
- 8: https://reintech.io/blog/understanding-implementing-swifts-exported
- 9: https://www.hackingwithswift.com/books/ios-swiftui/scheduling-local-notifications
Add import UserNotifications — code will not compile without it.
UNUserNotificationCenter is not re-exported by SwiftUI and requires an explicit import from the UserNotifications framework. Attempting to use it without the import results in a "Use of undeclared type" compilation error.
Fix
import SwiftUI
+import UserNotificationsAlso consider using OSLog/Logger instead of print for production logging, and move the notification permission request out of App.init() (e.g., to onAppear or a dedicated initialization method) so the UI controls when the OS prompt appears.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ios/pointron/Pointron/PointronApp.swift` around lines 8 - 22, Add the missing
import for the UserNotifications framework and move the notification permission
request out of PointronApp.init(): add "import UserNotifications" at the top,
and relocate the UNUserNotificationCenter.current().requestAuthorization(...)
call from the PointronApp init to a lifecycle-aware spot (e.g., onAppear of the
root view or a dedicated initializer method) so the OS prompt is shown at the
right time; while here, replace raw print calls inside that callback with a
Logger/OSLog-based logger to avoid using print in production and reference
PointronApp and UNUserNotificationCenter when applying the changes.
| { | ||
| "idiom" : "universal", | ||
| "platform" : "ios", | ||
| "size" : "1024x1024" | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check for actual image files in the AppIcon.appiconset directory
echo "=== Checking for icon image files ==="
fd -e png -e jpg . "ios/pointron/PointronWidgetExtension/Assets.xcassets/AppIcon.appiconset"Repository: 21nCo/nucleum
Length of output: 95
🏁 Script executed:
cat "ios/pointron/PointronWidgetExtension/Assets.xcassets/AppIcon.appiconset/Contents.json"Repository: 21nCo/nucleum
Length of output: 234
🏁 Script executed:
ls -la "ios/pointron/PointronWidgetExtension/Assets.xcassets/AppIcon.appiconset/"Repository: 21nCo/nucleum
Length of output: 228
Add the app icon image file to the asset catalog.
The AppIcon.appiconset directory is missing the actual image file. The Contents.json defines a 1024x1024 universal app icon but contains no filename reference, and no image files exist in the directory. Add the icon image file to this asset catalog, otherwise the widget extension will display a placeholder icon instead of your app icon.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@ios/pointron/PointronWidgetExtension/Assets.xcassets/AppIcon.appiconset/Contents.json`
around lines 3 - 7, The AppIcon.appiconset is missing the actual 1024x1024 image
and a filename entry in Contents.json; add a 1024x1024 PNG (e.g.,
AppIcon-1024.png) into the AppIcon.appiconset folder and update Contents.json to
include a "filename" field for the entry with "size":"1024x1024" (keeping
"idiom":"universal" and "platform":"ios"), and ensure the asset is included in
the widget extension target so the widget uses the real icon instead of a
placeholder.




Summary by cubic
Adds the Memotron and Nucleum SwiftUI apps (iOS + macOS) to the monorepo with
whisper.cppintegration for on-device transcription. Implements TIDY-453 and adds small web updates for native embedding and product resource cleanup.New Features
ios/memotronandios/nucleumprojects with iOS and macOS targets, SwiftUI apps, entitlements, and app icons.whisper.cppvia local vendor atios/vendor/whisper.cppandwhisper.xcframeworkwith Objective‑C++ wrapper and bridging header.memotron:andnucleum:.PosthogTelemetrybehindVITE_NATIVE_EMBED, switchedLogin.svelteto$state, minor calendar panel fix, and trimmedProduct.NUCLEUSbrowse/resources (removedcombination)..gitignorenow ignores iOS build artifacts and local vendor directories.Migration
npm run setup:ios:whisper(builds localwhisper.cppand XCFramework).whisper.xcframeworkis added and set to “Embed & Sign”.VITE_NATIVE_EMBED="true"to disable Posthog in the client.Written for commit 23ec91c. Summary will update on new commits.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation