Widgets are a first-class PostPad surface: a pinned note must show its latest rendered markdown on the home screen / desktop, updating without notifications. This doc records what Tauri 2 can and can't do (researched 2025–2026) and the path we're taking.
Tauri 2 has no native widget API, and there is no roadmap for one. The official request (tauri#9766, May 2024) was converted to an unowned discussion (#14555, Nov 2025) with no owner.
Widgets are still shippable — as a hybrid: the normal Tauri app plus native widget extensions (Swift WidgetKit on Apple, Kotlin Glance on Android) added into the Xcode/Gradle projects Tauri generates. This is a manual-but-solved pattern.
Recommendation: go hybrid. Do not wait for pure-Tauri widgets, and do not build a separate widget-only client.
The main app writes the latest rendered note into a shared local cache; the widget reads that cache. Widgets must not call the PostPad HTTP API directly — widget extensions have tight OS execution/network/battery budgets and refresh on the OS timeline, so direct HTTP is unreliable. The body is already rendered markdown, so render once in the app and let the widget be a dumb view.
- iOS / macOS: App Group shared container (a file or
UserDefaults(suiteName:)), thenWidgetCenter.reload. - Android: SharedPreferences / Jetpack DataStore (same app sandbox, no App Group), then the widget-update broadcast.
| Platform | Feasible | Mechanism | Effort | Risk |
|---|---|---|---|---|
| iOS WidgetKit | Yes (hybrid) | Swift Widget Extension target declared in project.yml; App Group |
Medium-high | Medium |
| Android App Widget | Yes (hybrid) | Kotlin Glance/RemoteViews in gen/android/; SharedPreferences |
Medium | Low-med |
| macOS WidgetKit | Yes (hybrid) | Same as iOS, shares the Swift widget code; App Group | Medium-high | Medium |
Tauri regenerates the native mobile projects, so hand edits can be wiped:
- iOS/macOS projects are XcodeGen-generated from
src-tauri/gen/apple/project.yml. Only things declared inproject.yml(or a custom template) survivetauri ios build. Direct.xcodeprojGUI edits are lost on regen — the widget target must be expressed inproject.yml. - Android (
gen/android/) tolerates hand edits better; widget code + manifest entries added under the app module persist across rebuilds.
Known limits: on Apple, the widget works only in release/TestFlight builds, not tauri dev, and signing must be correct or the widget silently fails — iterate the widget in Xcode directly. On Android, keep Glance layouts flat (nesting is buggy on some launchers).
s00d/tauri-plugin-widgets— the only real cross-platform attempt. JSON IR → SwiftUI (Apple) / Glance (Android) / Adaptive Cards (Windows) / HTML (desktop). Supplies the shared-cache storage + reload for free (appGroup/userDefaults/widgetContainer). ~v0.5, pre-1.0, single-maintainer, "experimental." Evaluate first; don't hard-depend.Choochmeque/tauri-apple-extensions— CLI that scaffolds Apple extension targets into XcodeGen and wires up App Groups. Ships mainly a Share extension today; useful as the scaffolding mechanism even if we write the widget ourselves.- Tracking: plugins-workspace#2738.
Two home-screen widgets live in the tracked Android project, apps/desktop/src-tauri/gen/android/app/src/main/java/com/postpad/app/widget/ (plain AppWidgetProvider + RemoteViews list services, no Glance/Compose, no extra dependencies):
- List — resizable; every entry with title, age, sender and a one-line summary. Tap an entry to open it in the app; ↻ refreshes.
- Single Entry — resizable; pinned to one entry chosen when the widget is added (reconfigurable): title, sender · age, and the body as text lines.
How data flows:
- App → widgets: the web UI calls
window.PostPadWidgets.sync(base, boxKey, entries)(aJavascriptInterfaceadded inMainActivity.onWebViewCreate) after every pad fetch; the bridge writes SharedPreferences and redraws all widgets. Leaving the PO Box syncs an empty key, which clears them. - Widgets on their own: every ~30 min (
updatePeriodMillis) and on ↻, the widget fetchesGET /v1/padwith the saved box key (off the main thread viagoAsync), so it stays fresh while the app is closed. This departs from the "widgets never call the API" rule above on purpose: that rule is about iOS/WidgetKit budgets; Android broadcast receivers can make a short request. - Widget → app: a tap opens
MainActivitywith the entry id; the web UI takes it viaPostPadWidgets.takeOpenEntry()(at launch, on resume, or on apostpad-widget-openevent when already running) and opens that entry. - Light/dark follow the system via
values/andvalues-night/widget_colors.xml(same palette as the app).
gen/android is now tracked in git (only gen/schemas is ignored) so the widgets survive tauri android init; release signing applies only when ~/.config/postpad/android/keystore.properties exists.
- Now (this slice): ship the Tauri app + Worker API. Widget code is not built yet — it's blocked on native tooling, not on the API. The
WidgetPinconcept (note_id + size, per requirements) is deferred; the app just fetches and renders note bodies, which is the same data a widget will show. - Widget spike (next): shared-cache write path in the Rust core after each note fetch:
- Apple → App Group container file +
WidgetCenter.reload. - Android → SharedPreferences + widget update broadcast.
- Apple → App Group container file +
- Apple widget: one SwiftUI WidgetKit target shared between iOS and macOS, declared in
project.ymlsotauri buildpreserves it. Enable App Groups on app + extension. Test in release/TestFlight (no dev preview). - Android widget: Glance widget in
gen/android/, flat layout. - Evaluate
tauri-plugin-widgetsearly — if its JSON IR expresses a status-board layout, it collapses steps 2–4 into one dependency using the same channels; if it's too limiting, hand-write the extensions (nothing is wasted — same App Group / SharedPreferences transport).
Effort/risk order: Android lowest, iOS/macOS medium-high (signing + no dev preview). Cross-cutting risk: all this tooling is community-grade and pre-1.0 — budget to maintain the native Swift/Kotlin ourselves rather than rely on a plugin staying alive.
tauri#9766 · discussions#14555 · plugins-workspace#2738 · tauri/discussions#3122 · s00d/tauri-plugin-widgets · Choochmeque/tauri-apple-extensions · developer.android.com/develop/ui/views/appwidgets · tauri.app/release/@tauri-apps/cli/v2.5.0 · tauri#14332