diff --git a/crates/perry-api-manifest/src/entries.rs b/crates/perry-api-manifest/src/entries.rs index f3d558a9d..3d43c8c47 100644 --- a/crates/perry-api-manifest/src/entries.rs +++ b/crates/perry-api-manifest/src/entries.rs @@ -2087,6 +2087,11 @@ pub static API_MANIFEST: &[ApiEntry] = &[ method("perry/system", "isDarkMode", false, None), method("perry/system", "getDeviceIdiom", false, None), method("perry/system", "getDeviceModel", false, None), + // Bug-report-flow utility: stable OS-version string per + // platform (e.g. `"15.2"`, `"macOS 14.5"`, `"Android 14"`). + // Common need for crash reports and telemetry; pairs with + // getDeviceModel / getAppVersion. + method("perry/system", "getOSVersion", false, None), method("perry/system", "getLocale", false, None), method("perry/system", "getAppVersion", false, None), method("perry/system", "getAppBuildNumber", false, None), diff --git a/crates/perry-dispatch/src/lib.rs b/crates/perry-dispatch/src/lib.rs index 3109b54f3..a85be1e92 100644 --- a/crates/perry-dispatch/src/lib.rs +++ b/crates/perry-dispatch/src/lib.rs @@ -2204,6 +2204,16 @@ pub static PERRY_SYSTEM_TABLE: &[MethodRow] = &[ args: &[], ret: ReturnKind::F64, }, + // Bug-report-flow utility: stable OS-version string per + // platform. Returns a NaN-boxed JS string the user can splice + // into crash reports / telemetry. Same dispatch shape as + // getDeviceModel. + MethodRow { + method: "getOSVersion", + runtime: "perry_system_get_os_version", + args: &[], + ret: ReturnKind::F64, + }, MethodRow { method: "audioSetOutputFilename", runtime: "perry_system_audio_set_output_filename", diff --git a/crates/perry-ui-android/src/lib.rs b/crates/perry-ui-android/src/lib.rs index 60da71f8e..cfbdeb138 100644 --- a/crates/perry-ui-android/src/lib.rs +++ b/crates/perry-ui-android/src/lib.rs @@ -2169,6 +2169,20 @@ pub extern "C" fn perry_system_audio_get_waveform(count: f64) -> f64 { pub extern "C" fn perry_system_get_device_model() -> i64 { audio::get_device_model() } +/// Bug-report-flow utility: stable OS-version string. Android stub — +/// native impl will read `Build.VERSION.RELEASE` via JNI. +#[no_mangle] +pub extern "C" fn perry_system_get_os_version() -> i64 { + perry_runtime::stub_diag::perry_stub_warn( + "perry_system_get_os_version", + "Android getOSVersion (Build.VERSION.RELEASE) not yet implemented", + None, + ); + extern "C" { + fn js_string_from_bytes(ptr: *const u8, len: i32) -> i64; + } + unsafe { js_string_from_bytes(std::ptr::null(), 0) } +} #[no_mangle] pub extern "C" fn perry_system_audio_set_output_filename(filename_ptr: i64) { fn str_from_header(ptr: *const u8) -> &'static str { diff --git a/crates/perry-ui-gtk4/src/lib.rs b/crates/perry-ui-gtk4/src/lib.rs index 88aa94f99..57fc80a82 100644 --- a/crates/perry-ui-gtk4/src/lib.rs +++ b/crates/perry-ui-gtk4/src/lib.rs @@ -1949,6 +1949,21 @@ pub extern "C" fn perry_system_audio_get_waveform(count: f64) -> f64 { pub extern "C" fn perry_system_get_device_model() -> i64 { audio::get_device_model() } +/// Bug-report-flow utility: stable OS-version string. GTK4/Linux +/// stub — native impl would read `/etc/os-release` or +/// `uname -r`. +#[no_mangle] +pub extern "C" fn perry_system_get_os_version() -> i64 { + perry_runtime::stub_diag::perry_stub_warn( + "perry_system_get_os_version", + "Linux getOSVersion (/etc/os-release) not yet implemented", + None, + ); + extern "C" { + fn js_string_from_bytes(ptr: *const u8, len: i32) -> i64; + } + unsafe { js_string_from_bytes(std::ptr::null(), 0) } +} #[no_mangle] pub extern "C" fn perry_system_audio_set_output_filename(filename_ptr: i64) { fn str_from_header(ptr: *const u8) -> &'static str { diff --git a/crates/perry-ui-ios/src/lib.rs b/crates/perry-ui-ios/src/lib.rs index 30644fd91..5e90ccecd 100644 --- a/crates/perry-ui-ios/src/lib.rs +++ b/crates/perry-ui-ios/src/lib.rs @@ -1541,6 +1541,20 @@ pub extern "C" fn perry_system_audio_get_waveform(count: f64) -> f64 { pub extern "C" fn perry_system_get_device_model() -> i64 { audio::get_device_model() } +/// Bug-report-flow utility: stable OS-version string. MVP stub on +/// iOS; native impl will use `[[UIDevice currentDevice] systemVersion]`. +#[no_mangle] +pub extern "C" fn perry_system_get_os_version() -> i64 { + perry_runtime::stub_diag::perry_stub_warn( + "perry_system_get_os_version", + "iOS getOSVersion not yet implemented (UIDevice.systemVersion follow-up)", + Some("#918"), + ); + extern "C" { + fn js_string_from_bytes(ptr: *const u8, len: i32) -> i64; + } + unsafe { js_string_from_bytes(std::ptr::null(), 0) } +} #[no_mangle] pub extern "C" fn perry_system_audio_set_output_filename(filename_ptr: i64) { fn str_from_header(ptr: *const u8) -> &'static str { diff --git a/crates/perry-ui-macos/src/lib.rs b/crates/perry-ui-macos/src/lib.rs index 9330238f9..2d5772e32 100644 --- a/crates/perry-ui-macos/src/lib.rs +++ b/crates/perry-ui-macos/src/lib.rs @@ -2157,6 +2157,39 @@ pub extern "C" fn perry_system_get_device_model() -> i64 { audio::get_device_model() } +/// Bug-report-flow utility: stable OS-version string. Uses +/// `[[NSProcessInfo processInfo] operatingSystemVersionString]` +/// which returns a human-readable form like `"Version 14.5 +/// (Build 23F79)"` — preserved as-is so triage can grep the build +/// number too. Returned as a Perry-managed string. +#[no_mangle] +pub extern "C" fn perry_system_get_os_version() -> i64 { + extern "C" { + fn js_string_from_bytes(ptr: *const u8, len: i32) -> i64; + } + unsafe { + let cls = objc2::runtime::AnyClass::get(c"NSProcessInfo").unwrap(); + let info: *mut objc2::runtime::AnyObject = objc2::msg_send![cls, processInfo]; + if info.is_null() { + return js_string_from_bytes(std::ptr::null(), 0); + } + let s: *mut objc2::runtime::AnyObject = + objc2::msg_send![info, operatingSystemVersionString]; + if s.is_null() { + return js_string_from_bytes(std::ptr::null(), 0); + } + let utf8_ptr: *const u8 = objc2::msg_send![s, UTF8String]; + if utf8_ptr.is_null() { + return js_string_from_bytes(std::ptr::null(), 0); + } + let utf8_len: usize = objc2::msg_send![s, lengthOfBytesUsingEncoding: 4u64]; + if utf8_len == 0 { + return js_string_from_bytes(std::ptr::null(), 0); + } + js_string_from_bytes(utf8_ptr, utf8_len as i32) + } +} + /// Set output filename for audio recording. #[no_mangle] pub extern "C" fn perry_system_audio_set_output_filename(filename_ptr: i64) { diff --git a/crates/perry-ui-test/src/lib.rs b/crates/perry-ui-test/src/lib.rs index c66e8465c..d1519165d 100644 --- a/crates/perry-ui-test/src/lib.rs +++ b/crates/perry-ui-test/src/lib.rs @@ -1463,6 +1463,20 @@ pub const FEATURES: &[Feature] = &[ web: S, web_name: None, }, + // Bug-report-flow utility: stable OS-version string. macOS has + // the real impl via NSProcessInfo; every other platform is a + // first-call-warning stub awaiting its native implementation. + Feature { + name: "perry_system_get_os_version", + category: SystemApi, + macos: S, + ios: S, + android: S, + gtk4: S, + windows: S, + web: S, + web_name: None, + }, Feature { name: "perry_system_preferences_set", category: SystemApi, diff --git a/crates/perry-ui-tvos/src/lib.rs b/crates/perry-ui-tvos/src/lib.rs index 212c6bf69..98be0c151 100644 --- a/crates/perry-ui-tvos/src/lib.rs +++ b/crates/perry-ui-tvos/src/lib.rs @@ -1327,6 +1327,21 @@ pub extern "C" fn perry_system_audio_get_waveform(count: f64) -> f64 { pub extern "C" fn perry_system_get_device_model() -> i64 { audio::get_device_model() } +/// Bug-report-flow utility: stable OS-version string. tvOS stub — +/// native impl can mirror iOS's `[[UIDevice currentDevice] systemVersion]` +/// (UIDevice is available on tvOS). +#[no_mangle] +pub extern "C" fn perry_system_get_os_version() -> i64 { + perry_runtime::stub_diag::perry_stub_warn( + "perry_system_get_os_version", + "tvOS getOSVersion not yet implemented", + None, + ); + extern "C" { + fn js_string_from_bytes(ptr: *const u8, len: i32) -> i64; + } + unsafe { js_string_from_bytes(std::ptr::null(), 0) } +} #[no_mangle] pub extern "C" fn perry_system_audio_set_output_filename(filename_ptr: i64) { fn str_from_header(ptr: *const u8) -> &'static str { diff --git a/crates/perry-ui-visionos/src/lib.rs b/crates/perry-ui-visionos/src/lib.rs index 9d8fbc7e3..bdb8511f8 100644 --- a/crates/perry-ui-visionos/src/lib.rs +++ b/crates/perry-ui-visionos/src/lib.rs @@ -1448,6 +1448,19 @@ pub extern "C" fn perry_system_audio_get_waveform(count: f64) -> f64 { pub extern "C" fn perry_system_get_device_model() -> i64 { audio::get_device_model() } +/// Bug-report-flow utility: stable OS-version string. visionOS stub. +#[no_mangle] +pub extern "C" fn perry_system_get_os_version() -> i64 { + perry_runtime::stub_diag::perry_stub_warn( + "perry_system_get_os_version", + "visionOS getOSVersion not yet implemented", + None, + ); + extern "C" { + fn js_string_from_bytes(ptr: *const u8, len: i32) -> i64; + } + unsafe { js_string_from_bytes(std::ptr::null(), 0) } +} #[no_mangle] pub extern "C" fn perry_system_audio_set_output_filename(filename_ptr: i64) { fn str_from_header(ptr: *const u8) -> &'static str { diff --git a/crates/perry-ui-watchos/src/lib.rs b/crates/perry-ui-watchos/src/lib.rs index 67edbb6b8..5bf320244 100644 --- a/crates/perry-ui-watchos/src/lib.rs +++ b/crates/perry-ui-watchos/src/lib.rs @@ -1200,6 +1200,19 @@ pub extern "C" fn perry_system_audio_get_waveform(_count: f64) -> f64 { pub extern "C" fn perry_system_get_device_model() -> i64 { 0 } +/// Bug-report-flow utility: stable OS-version string. watchOS stub. +#[no_mangle] +pub extern "C" fn perry_system_get_os_version() -> i64 { + perry_runtime::stub_diag::perry_stub_warn( + "perry_system_get_os_version", + "watchOS getOSVersion not yet implemented", + None, + ); + extern "C" { + fn js_string_from_bytes(ptr: *const u8, len: i32) -> i64; + } + unsafe { js_string_from_bytes(std::ptr::null(), 0) } +} #[no_mangle] pub extern "C" fn perry_system_audio_set_output_filename(filename_ptr: i64) { let filename = str_from_header(filename_ptr as *const u8); diff --git a/crates/perry-ui-windows/src/lib.rs b/crates/perry-ui-windows/src/lib.rs index cd4d76382..21887f737 100644 --- a/crates/perry-ui-windows/src/lib.rs +++ b/crates/perry-ui-windows/src/lib.rs @@ -1994,6 +1994,20 @@ pub extern "C" fn perry_system_audio_get_waveform(count: f64) -> f64 { pub extern "C" fn perry_system_get_device_model() -> i64 { audio::get_device_model() } +/// Bug-report-flow utility: stable OS-version string. Windows stub — +/// native impl will use `GetVersionEx` / `RtlGetVersion`. +#[no_mangle] +pub extern "C" fn perry_system_get_os_version() -> i64 { + perry_runtime::stub_diag::perry_stub_warn( + "perry_system_get_os_version", + "Windows getOSVersion (RtlGetVersion) not yet implemented", + None, + ); + extern "C" { + fn js_string_from_bytes(ptr: *const u8, len: i32) -> i64; + } + unsafe { js_string_from_bytes(std::ptr::null(), 0) } +} #[no_mangle] pub extern "C" fn perry_system_audio_set_output_filename(filename_ptr: i64) { fn str_from_header(ptr: *const u8) -> &'static str { diff --git a/docs/api/perry.d.ts b/docs/api/perry.d.ts index d6f8e8e40..75ed849d2 100644 --- a/docs/api/perry.d.ts +++ b/docs/api/perry.d.ts @@ -1,6 +1,6 @@ // Auto-generated from Perry's API manifest (#465). Do not edit by hand. // Source: perry-api-manifest::API_MANIFEST -// Coverage: 900 entries across 71 modules +// Coverage: 901 entries across 71 modules declare module "argon2" { /** stdlib */ @@ -736,6 +736,8 @@ declare module "perry/system" { /** stdlib */ export function getLocale(...args: any[]): any; /** stdlib */ + export function getOSVersion(...args: any[]): any; + /** stdlib */ export function imagePickerPick(...args: any[]): any; /** stdlib */ export function isDarkMode(...args: any[]): any; diff --git a/docs/src/api/reference.md b/docs/src/api/reference.md index 6dbc5f150..3f089f414 100644 --- a/docs/src/api/reference.md +++ b/docs/src/api/reference.md @@ -2,7 +2,7 @@ This page is auto-generated from Perry's compile-time API manifest (`perry-api-manifest::API_MANIFEST`). It is the source of truth for what `perry compile` accepts; references to symbols not listed here produce `R005 UnimplementedApi` (issue #463). Stubs (#464) are flagged ⚠ — they link cleanly but no-op at runtime on the chosen target. -Total: 900 entries across 71 modules. +Total: 901 entries across 71 modules. ## Modules @@ -922,6 +922,7 @@ Total: 900 entries across 71 modules. - `getDeviceIdiom` — module - `getDeviceModel` — module - `getLocale` — module +- `getOSVersion` — module - `imagePickerPick` — module - `isDarkMode` — module - `keychainDelete` — module