Rev
Most of my open source work starts the same way. I'm building an app, a library I depend on breaks or stops one feature short, and I fix it upstream instead of carrying a patch. These are the contributions that got merged, grouped by project.

Expo

@ExpoModule macro support for inline modules

expo/expo#51047 · Oct 2026 · iOS autolinking
Expo Modules 2.0 added a Swift macro, @ExpoModule, as a lighter way to declare a native module. Inline modules, the native files that live inside the app instead of a separate package, ignored it on iOS for two reasons:
  • Autolinking only treated a Swift file as a module if it contained func definition() -> ModuleDefinition. A macro-based module has no definition() function, so it was never registered.
  • The compiler flags that load the macro plugin were only added to pods that depend on ExpoModulesCore. Inline modules compile in the app target, so @ExpoModule and @JS failed to expand there.
A module like this one, taken from the new tests, was skipped:
import ExpoModulesCore @ExpoModule public final class GreeterModule: Module { @JS func greet(name: String) -> String { "Hi, \(name)" } }
The fix makes autolinking detect @ExpoModule, with or without arguments, and appends the macro plugin flags to the app target's xcconfig for every build configuration. It reuses the existing append_macro_flags helper, so running pod install again doesn't duplicate flags or drop ones written by earlier hooks. New unit tests cover both macro forms. They failed before the change and pass after it. I ran the same change as a patch in a production app before opening the PR.
// Expo Modules 2.0 classes declare `@ExpoModule` instead of a `definition()` DSL. const swiftExpoModuleMacroRegex = /@ExpoModule\b/; return swiftModuleDefinitionRegex.test(contents) || swiftExpoModuleMacroRegex.test(contents);
inlineModules.ts
# Inline modules compile in the app target itself, so its xcconfigs need the plugin too. target.user_build_configurations.each_key do |build_configuration_name| xcconfig_path = target.xcconfig_path(build_configuration_name) append_macro_flags(target.build_settings(build_configuration_name), xcconfig_path, macro_flags) end
project_integrator.rb

Permission hooks calling native methods with undefined

expo/expo#39594 · Sep 2025 · expo-modules-core
useLocationPermissions from expo-maps threw at runtime. The shared permission hook in expo-modules-core always passed an options argument to the native get and request methods, even when there were no options, so it sent undefined to native functions that take no arguments. The fix calls those methods with no argument when the options object is empty. I updated the existing tests to assert the call is made with no arguments instead of undefined.
-const response = await methods.getMethod( - Object.keys(permissionOptions).length > 0 ? (permissionOptions as Options) : undefined -); +let response: Permission; +if (Object.keys(permissionOptions).length > 0) { + response = await methods.getMethod(permissionOptions as Options); +} else { + response = await methods.getMethod(); +}
PermissionsHook.ts (the same change applies to requestMethod)

Voltra

Voltra by Callstack lets React Native developers build iOS Live Activities, Dynamic Island layouts and widgets as React components, without writing Swift. I added two features.

Supplemental activity families (iOS 18+)

iOS 18 can show a Live Activity in the Apple Watch Smart Stack and on CarPlay through supplementalActivityFamilies. Supporting it meant changes in all three layers of the library:
  • TypeScript: a new supplementalActivityFamilies.small variant on useLiveActivity, which falls back to the lock screen layout when it isn't set.
  • Swift: a new region and an adaptive lock screen view that reads @Environment(\.activityFamily) to pick the layout, with availability checks so older iOS versions keep working.
  • Config plugin: an opt-in liveActivity.supplementalActivityFamilies option that generates the widget wrapper and validates the config.
{ "expo": { "plugins": [ ["voltra", { "groupIdentifier": "group.com.example", "liveActivity": { "supplementalActivityFamilies": ["small"] } }] ] } }
app.json
useLiveActivity({ // ...your other variants supplementalActivityFamilies: { small: <CompactWatchView />, }, });
It's around 1,000 added lines across 16 files. Because it's opt-in, existing apps see no change.
A workout Live Activity in the Apple Watch Smart Stack, from the PR.
A workout Live Activity in the Apple Watch Smart Stack, from the PR.

showHours on Timer

A 94-minute countdown rendered as 94:00. The new showHours prop switches it to 1:34:00 using SwiftUI's Text(timerInterval:showsHours:), and the formatter behind custom text templates respects it too. I added the prop to the component schema and regenerated the TypeScript and Swift bindings from it rather than editing them by hand.
<Voltra.Timer endAtMs={endAt} showHours />
Before: a two-hour countdown reads 119:54.
Before: a two-hour countdown reads 119:54.
After, with showHours: 1:59:59.
After, with showHours: 1:59:59.

Vorssaint Utils

Vorssaint Utils is a free macOS menu bar toolkit with a window switcher. To switch between windows of the same app, you had to open the Apps switcher first and then press the Windows shortcut. Now the Windows shortcut opens a switcher scoped to the frontmost app directly. Hold the modifiers to keep it open, tap the key to cycle, release to focus. It follows the same lifecycle as the Apps switcher, handles single-window and zero-window apps, and the settings caption is updated in every supported language.

Smaller fixes

  • apple-docs-mcp#31: added AlarmKit to the recognised frameworks so AI assistants can search its docs.

Each of these started as a problem in something I was building. Sending the fix upstream means the next person doesn't have to find the same bug.