Commit Graph
12 Commits
Author SHA1 Message Date
Edison Jwa 112a563de5 build(macos): link local SileroCoreML package 2026-06-02 01:54:05 +09:00
EdisonJwa a77a9b2ef2 docs(sdd-119)+feat(macos): consolidate deployment-target SoT (SDD-119 amendment v0.9.17)
Resolve the macOS half of the SDD-119 item 3 single-source-of-truth follow-up. The chanora_bridge cdylib macOS deployment-target floor ('10.15') was previously hard-coded at 7 sites across Podfile and chanora_bridge.podspec; this commit collapses them to a single Ruby constant declaration in a new SoT file.

Selected Option B (Ruby constant) over Option A (.xcconfig — rejected because the podspec prepare_command runs before any xcconfig is applied) and Option C (versioned text file — rejected as overkill given both consumers are already Ruby). Realizes SAD-087(a)'s 'single macOS build-configuration location' mandate.

Out of scope: the pbxproj 'MACOSX_DEPLOYMENT_TARGET = 10.15' lines (565/666/717) belong to the PBXProject default config and are independently overridden to '11.0' at the Runner PBXNativeTarget level; they are the Runner app's floor, not the bridge cdylib's floor. The iOS half (IPHONEOS_DEPLOYMENT_TARGET=13.0) remains an open follow-up.

Files: new apps/chanora_flutter/macos/macos_deployment_target.rb (MACOS_BRIDGE_DEPLOYMENT_TARGET = '10.15'.freeze); Podfile + chanora_bridge.podspec require_relative the constant and consume it at 7 sites; docs/architecture/sdd.md SDD-119 item 3 rewritten + Notes bullet updated + v0.9.17 changelog entry.
2026-05-18 14:39:14 +08:00
Edison Jwa 00692b76a2 fix(macOS): enable Hardened Runtime + outgoing network for all configs
Debug and Profile had ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO which
blocked outbound TCP. All three configs now have:
- ENABLE_HARDENED_RUNTIME = YES
- ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES

This was the root cause of the 'Operation not permitted' connection
failure on macOS Sequoia.
2026-05-17 22:32:30 +09:00
Edison Jwa 72c6e14797 feat: modern macOS window chrome + Linux build script
macOS:
- Transparent title bar with hidden title, full-size content view
- macOS: inline Row header (no AppBar) with 56px traffic-light pad
- Other platforms: standard Material AppBar unchanged
- App name 'Chanora' in CFBundleName/CFBundleDisplayName (iOS + macOS)
- NSLocalNetworkUsageDescription added to both platforms

Linux:
- tools/build-linux.sh: builds Rust .so + Flutter bundle + tarball
- Verifies GTK3, libopus dev headers, Rust target
- Copies libchanora_bridge.so into bundle/lib/
2026-05-17 22:29:42 +09:00
Edison Jwa 63b102ed27 feat: add NSLocalNetworkUsageDescription to iOS and macOS Info.plist
Required for local network privacy prompt on macOS 15+ and iOS 14+.
App appears in System Settings → Local Network after connecting to a
LAN server. Internet-hosted servers only need network.client entitlement.
2026-05-17 22:10:39 +09:00
Edison Jwa 4ecea09bbc feat: add permission-denied dialog for macOS network/mic access
When macOS denies network access (PermissionDenied), show a localized
dialog explaining how to grant permission in System Settings, with
an 'Open System Settings' button that opens directly to the Local
Network privacy pane.

Also adds author info (Edison Jwa) to About dialog and moves
diagnostics button from AppBar into About dialog.
2026-05-17 21:57:56 +09:00
Edison Jwa 618b6930fe feat: add macOS bridge podspec, Windows project, sync versions to 0.2.0-beta.1 2026-05-17 21:42:54 +09:00
Edison Jwa 7a59f5b9a1 feat(ios,p0): iOS P0 platform, audio fixes, channel UX 2026-05-17 22:00:00 +09:00
EdisonJwa d4c04b6a72 fix(ui,audio,ios,macos): eight P0 mobile fixes
User report from sideloaded iPhone build, in order of priority:

  #4 'Could not join channel: audio: audio backend:
     build_output_stream: The requested stream configuration is
     not supported by the device.'

     Cause: we forced cpal::BufferSize::Fixed(2048) on the output
     and input streams unconditionally on non-Linux. iOS CoreAudio
     RemoteIO units reject arbitrary buffer-size requests with that
     exact error. Windows WASAPI needs the pinning for shared-mode
     jitter, but macOS / iOS do not.
     Fix: cfg-gate Fixed(2048) to target_os = 'windows'; everywhere
     else use BufferSize::Default and let the platform HAL pick.
     crates/chanora_audio/src/engine.rs.

  #5 'Could not join channel: invariant violated:
     voice_in already taken'

     Cause: start_audio tore down the old engine BEFORE attempting
     to construct the new one, and consumed voice_in (an mpsc
     Receiver that can only be taken once) early. When the new
     engine failed mid-construction (e.g. because of #4 above) the
     session was left with: no audio engine, voice_in consumed,
     no way to retry without reconnect. The second voice_join
     attempt surfaced the invariant message.
     Fix: build the new engine BEFORE tearing down the old. Only
     swap state.audio if construction succeeded. crates/chanora_
     core/src/lib.rs::ChanoraSession::start_audio. Additionally
     added a put_voice_in helper to the protocol adapter (
     crates/chanora_protocol/src/adapter.rs) for a future
     broadcast-channel migration; the helper is unused on the
     immediate fix path but documents the intent.

  #3 'permission request would better on first open'

     Cause: AVAudioSession only triggers the mic-permission
     prompt the first time it tries to record. We never recorded
     until voice_join, so the prompt fired then.
     Fix iOS: AVAudioSession.sharedInstance().requestRecordPermission
     in AppDelegate.swift::application(_:didFinishLaunchingWithOptions:).
     Fix macOS: AVCaptureDevice.requestAccess(for: .audio) in
     macos/Runner/AppDelegate.swift::applicationDidFinishLaunching.
     Both run non-blocking; user can deny without crashing app
     launch, and voice_join then surfaces a clearer downstream
     error when the engine fails to open the input device.

  #1 + #2 'one-column upper takes too much space; Push to Talk
           button at bottom would be better'

     Layout rework for narrow-mode (single column, mobile shape):
     - Flipped the stacking order in main.dart so Voice Bar moves
       to the BOTTOM of the body and the channel tree (Expanded)
       fills above. Wide-mode (Row, >= 840 dp) layout unchanged.
     - Inside the Voice Bar on touch-only hosts, moved the
       on-screen Push to Talk button to be the LAST element of
       the Voice Bar (was Row 3). Order now: pill + mutes, mode
       badge + settings, level meter, stats line, release-tail
       caption, PTT button. The button is closest to the user's
       thumb when the Voice Bar is pinned to the bottom of a
       narrow-layout screen.

  #6 'remove right top debug badge'

     debugShowCheckedModeBanner: false on the MaterialApp.
     Release builds never showed it anyway; this only affects
     local dev / debug builds.

  #7 'what does the refresh button use for? nothing happened'

     Removed. The snapshot updates via BridgeEvent::SnapshotChanged
     are pushed from the bridge — a manual rust.snapshot() call
     was redundant. Now only the Diagnostics + Disconnect actions
     remain in the AppBar trailing row when connected.

  #8 'Bind Key related function should not be added to a mobile
     platform'

     widgets/voice_settings.dart: bind-key OutlinedButton is now
     #cfg'd out when Platform.isIOS || Platform.isAndroid. The
     release-tail slider stays because it still applies to the
     on-screen PTT button. Capability badge in voice_bar.dart
     also hidden on mobile (it would always show L0Focused which
     is redundant with the visible on-screen button).

Tests + analyze: chanora_audio 34/0/0 on macOS, workspace 78/0/1
on Linux; flutter analyze clean (6 pre-existing Radio.groupValue
infos). flutter build ios --release --no-codesign: 28.8 s clean
(Runner.app 29.9 MB).
2026-05-16 15:41:08 +08:00
EdisonJwa f5d3810f5f feat(ios,macos): Apple privacy manifest (PrivacyInfo.xcprivacy)
Apple has enforced a `PrivacyInfo.xcprivacy` privacy manifest at App
Store submission since May 2024 for iOS / iPadOS / visionOS /
watchOS, and rolled the requirement out to macOS in late 2024.
Without the file, App Store Connect rejects archive uploads with
"missing required privacy manifest". This commit adds the manifest
for both iOS and macOS Runner targets.

apps/chanora_flutter/ios/Runner/PrivacyInfo.xcprivacy
apps/chanora_flutter/macos/Runner/PrivacyInfo.xcprivacy
  Identical content. Declarations:

  NSPrivacyCollectedDataTypes:
    NSPrivacyCollectedDataTypeAudioData
      Microphone audio transmitted to the user's chosen voice
      server while connected and unmuted. Not linked to user
      identity (no Apple ID / IDFA tied), not used for tracking.
      Purpose: AppFunctionality (communications).

  NSPrivacyTracking: false
  NSPrivacyTrackingDomains: []
    Chanora performs no cross-app / cross-website tracking.

  NSPrivacyAccessedAPITypes:
    FileTimestamp (C617.1)
      tokio + rusqlite file I/O for identity.tskey, chanora.db,
      audio_meta.json, chanora.log inside the app container.
    UserDefaults (CA92.1)
      Indirect via path_provider Flutter plugin querying for
      Application Support / Documents directories.
    SystemBootTime (35F9.1)
      tracing-subscriber timestamps log records relative to boot.
    DiskSpace (85F4.1)
      rusqlite checks before sqlite page writes.

  All four "required reason" API categories use Apple's published
  allow-list reason codes; no fingerprinting / analytics usage.

apps/chanora_flutter/ios/Runner.xcodeproj/project.pbxproj
apps/chanora_flutter/macos/Runner.xcodeproj/project.pbxproj
  Added PrivacyInfo.xcprivacy to the Runner group and to the
  Runner target's "Copy Bundle Resources" build phase via the
  xcodeproj Ruby gem (via a one-shot script). With this, the file
  is placed at Runner.app/PrivacyInfo.xcprivacy where Apple's
  validator looks for it — `find Runner.app -name
  PrivacyInfo.xcprivacy` shows our manifest at the bundle root
  alongside Flutter's and connectivity_plus's.

Verified on the M1 Mac (coder@100.118.130.73):
  flutter build ios --release --no-codesign                4.0 s
    -> Runner.app/PrivacyInfo.xcprivacy present
  flutter build ipa --release --no-codesign                28.4 s
    -> Runner.xcarchive built (171.4 MB)
    -> archive's Runner.app/PrivacyInfo.xcprivacy present
    -> archive's Runner.app/Frameworks/chanora_bridge.framework
       built fresh via the chanora_bridge.podspec prepare_command
       under xcodebuild's sandbox (no PATH / env weirdness).

P1 follow-ups noted by xcodebuild's validator (not blockers for
this commit but for App Store submission):
  * Real app icon (currently default placeholder)
  * Real launch image (currently default placeholder)
  * Paid Apple Developer Program account, registered App ID, and
    Distribution provisioning profile (Personal Team sideloads
    still work as today).
2026-05-16 16:03:39 +09:00
EdisonJwa e3d7017dd9 feat(macos,ios): entitlements + permission strings + bundle-glue script
macOS:
  * Runner/DebugProfile.entitlements + Release.entitlements: add
    com.apple.security.network.client (outbound TS3 server connect)
    and com.apple.security.device.audio-input (microphone capture).
    Debug keeps com.apple.security.network.server + cs.allow-jit
    (Flutter hot-reload needs both); Release drops them.
  * Runner/Info.plist: add NSMicrophoneUsageDescription and
    NSInputMonitoringUsageDescription so the macOS system prompts
    show a sensible explanation when Chanora first needs mic or
    Input Monitoring access. Input Monitoring is required by
    CGEventTapCreate (SDD-085).
  * Runner.xcodeproj/project.pbxproj: switch Debug/Release/Profile
    code-signing from Automatic + Apple Development to Manual +
    "Sign to Run Locally" (CODE_SIGN_IDENTITY = -). This lets
    `flutter build macos --release` work over SSH where the login
    keychain is locked. The owner re-enables the personal team
    locally in Xcode for physical-device iOS testing later.

iOS:
  * Runner/Info.plist: add NSMicrophoneUsageDescription and the
    UIBackgroundModes = ['audio'] entry so voice traffic continues
    when the app is backgrounded (TS3 servers drop clients on idle
    audio streams).

tools/macos-postbuild.sh: new script. flutter build macos --release
emits build/macos/Build/Products/Release/chanora_flutter.app but
does NOT bundle libchanora_bridge.dylib. FRB on macOS dlopen()s the
bridge as chanora_bridge.framework/chanora_bridge, not a plain
dylib. This script:

  1. Wraps target/release/libchanora_bridge.dylib in a proper
     chanora_bridge.framework (Versions/A layout, Info.plist,
     Resources, symlinks).
  2. Rewrites LC_ID_DYLIB to
     @rpath/chanora_bridge.framework/chanora_bridge.
  3. Ad-hoc codesigns the framework and the .app bundle.
  4. Verifies with codesign --verify --deep --strict.

macOS analogue of buildit.cmd on Windows. Auto-integration into
Xcode build phases via cargokit / corrosion is a P1 carryover.

Verified end-to-end on the M1 Mac:
  cargo build --release -p chanora_bridge          11.76 s
  flutter build macos --release                    ok (59.2 MB)
  tools/macos-postbuild.sh Release                 ok
  chanora_flutter.app launch via SSH               bridge initialised,
                                                   identity + bookmark
                                                   store initialised
                                                   (~5 s smoke).
  ~/Library/Logs/app.chanora.chanora_flutter/chanora.log captures
  the boot sequence cleanly.

DEC-025 reference: macOS desktop is officially in scope.
2026-05-16 14:26:13 +09:00
EdisonJwa 4d57d189c9 feat(flutter,macos,ios): scaffold platform Xcode projects via flutter create
Ran `flutter create --platforms=macos,ios --project-name=chanora_flutter
--org=app.chanora .` on the M1 Mac to generate the standard Flutter
platform-specific scaffolding (Runner.xcodeproj, Podfile, AppDelegate,
entitlements, etc.) for both macOS and iOS.

The cross-platform Dart source (lib/) and Rust workspace (crates/,
core/) carry the actual application logic; these scaffolds are
required only so flutter build macos / ios can resolve their Xcode
projects. No application code added.

macOS smoke-launch from the M1 Mac verified the bridge dylib load
path: after manually wrapping libchanora_bridge.dylib into a proper
chanora_bridge.framework bundle (FRB on macOS expects a framework,
not a plain dylib) and codesigning ad-hoc, the runner starts cleanly
through 'bridge initialised', 'identity store initialised', 'bookmark
store initialised' just like the Linux runner.

Following commits will:
  * Wire the framework-bundling step into build glue (currently manual
    install_name_tool + codesign).
  * Replace the macOS PTT backend stub (crates/chanora_audio/src/
    ptt_backends/macos.rs) with live IOHIDCheckAccess +
    CGEventTapCreate so the descriptor advertises real L2/L3 capability
    on a permission-granted box (SDD-085).
  * Add the iOS AVAudioSession PlayAndRecord+voiceChat wiring.
  * Add docs/verification/macos-p0-acceptance.md and ios-p0-acceptance.md.
2026-05-16 14:07:33 +09:00