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 nodefinition()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@ExpoModuleand@JSfailed 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);
# 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
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(); +}
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+)
callstackincubator/voltra#13 · Jan 2026
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.smallvariant onuseLiveActivity, 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.supplementalActivityFamiliesoption that generates the widget wrapper and validates the config.
{ "expo": { "plugins": [ ["voltra", { "groupIdentifier": "group.com.example", "liveActivity": { "supplementalActivityFamilies": ["small"] } }] ] } }
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.
showHours on Timer
callstackincubator/voltra#22 · Jan 2026
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 />
Vorssaint Utils
vorssaint/vorssaint-utils#465 · Aug 2026
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
- react-native-awesome-slider#89: support for a custom container component, which I'd been using in a personal project.
- apple-docs-mcp#31: added AlarmKit to the recognised frameworks so AI assistants can search its docs.
- expo-apple-targets#72: added the missing
chalkdependency that broke the plugin on install.
- Swift-Concurrency-Agent-Skill#2: fixed a SKILL.md description that stopped the skill from loading in OpenCode.
- expansion-pack#20: added a tRPC expansion pack to the Next.js starter add-ons.
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.