- Kotlin 39.8%
- Objective-C++ 29.7%
- JavaScript 17.4%
- Ruby 4.6%
- Objective-C 3.4%
- Other 5.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
ci/woodpecker/push/apps/1 Pipeline was successful
ci/woodpecker/push/apps/2 Pipeline was successful
ci/woodpecker/push/apps/3 Pipeline was successful
ci/woodpecker/push/android Pipeline was successful
ci/woodpecker/push/draft-release Pipeline was successful
ci/woodpecker/tag/release Pipeline was successful
MainActivity is singleTask, so a VIEW intent delivered while the address bar is open came in through onNewIntent and was dropped. |
||
| .woodpecker | ||
| apple/Unbundled | ||
| examples | ||
| host | ||
| host-ios | ||
| host-macos | ||
| metro | ||
| scripts | ||
| tools | ||
| .gitignore | ||
| .taskrc.yml | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| DESIGN.md | ||
| DEVELOPER.md | ||
| mise.toml | ||
| README.md | ||
| Taskfile.yml | ||
Unbundled
What if a native app were just a URL?
The web has an incredible distribution model. You send someone a link, they tap it, and they are using your software. There is no store and no install step, and updates arrive on the next visit. It is the reason the web won, and it came bundled with HTML, CSS and the DOM.
Native apps got the better UI toolkit. They pay for it with packaging, installing and updating each app as its own binary, which makes store review, "please update" prompts and 200 MB downloads for a to-do list normal.
Unbundled pulls those two apart. It keeps the web's distribution model and puts ordinary native React Native apps underneath it.
https://notes.example/note/123
Tap that on a phone with the Unbundled runtime installed and you get a real native app, open on note 123. Nothing was installed first. Other apps you open live alongside it, each isolated by its origin just as websites are.
How it works
One native app per platform, the runtime, contains React Native, Hermes and a fixed set of native modules. Applications are only JavaScript and assets on an ordinary static web server:
npm run build # → dist/
deploy dist/ to https://notes.example
When the runtime is given a URL, it:
- Finds the application from the URL's origin, via
/.well-known/origin-runtime.json. - Downloads only the entry bundle. Everything behind a
React.lazy(() => import(...))stays on the server until the app actually needs it. - Starts the app in its own React Native instance, tied to that origin.
- Hands the URL to the app through normal React Native
Linking, so its own router decides what/note/123means.
The apps don't import any Unbundled SDK. They are plain React Native with React Navigation,
AsyncStorage, PermissionsAndroid and Linking. The runtime works underneath those APIs:
- Storage. Each app's AsyncStorage belongs to its origin, so two apps can both store a
"theme"key and never see each other's. - Permissions. Android grants a permission to the runtime as a whole. An app also needs its own grant, so allowing location for Notes does not allow it for Calendar.
- Links.
Linking.openURL("https://calendar.example/today")from inside Notes starts Calendar in its own task. A link to an ordinary website goes to the browser. - Updates. There is no update step. Code is cached by content hash, so a new release downloads only what changed, and only when it's used.
Downloaded apps can't bring their own native code. They get the native modules the runtime was built with and nothing else, which is also what keeps the security boundary meaningful.
Status
Unbundled is an experimental prototype. Every milestone in the original plan works on an Android emulator, including the full end-to-end scenario. That scenario opens a shared link into a Notes app that has never been installed, lazy-loads its editor, keeps its storage and permissions separate, and starts a second app from a link.
Runtimes for iOS and macOS run the same deployed release natively: one origin, one build of the app per platform, no install anywhere. On iOS, Notes and Calendar run with per-origin storage too. Permission isolation exists only on Android so far.
It has not been hardened for untrusted code, and it has only run against local development servers so far.
What the platforms allow
This is a proof of concept, and it stays inside the walls each platform has built. Some features need the platform to play ball. The prototype shows they are technically feasible; whether they can ship depends on the platform owner.
- Android is the most open. The runtime can own links through App Links, run as a service, and give each app its own task in Recents. Google Play's policy on downloaded code exempts code run by an interpreter with only indirect access to Android APIs, which is close to what this is, and sideloading is always possible.
- macOS is open enough. The runtime can run as a background agent, open a window per app, and ship as a notarized download outside the App Store.
- iOS and iPadOS are where the walls are. iPad gives each app its own window, but iPhone
shows one window per app, so there the apps share it. There are no user-installable
background services, so the runtime only lives while it's in the foreground. It can't claim
arbitrary
httpslinks: universal links cover only domains it owns, and custom schemes trigger a confirmation prompt. The App Store's rules on downloaded code rule out publishing it. None of this is a technical limit of the model. Apple could make it possible, and the closest existing door is the default-browser entitlement, which already routeshttp(s)links to a chosen app.
Read more
- DESIGN.md covers the architecture, how each piece works, and what the prototype found.
- DEVELOPER.md covers building the runtime and apps and running them on an emulator.