| name |
pythonide-appui |
| description |
Build or modify PythonIDE AppUI MiniApps with native navigation, forms, lists, search, tabs, toolbars, presentation, storage, and AppUI media bridges. Use when the user wants an interactive iOS MiniApp, settings screen, list/detail app, or native UI that may also need photos, camera, location, maps, video, or share. |
| license |
MIT |
| version |
1.1.0 |
| last_updated |
2026-06-19 |
| user_invocable |
true |
Execution rulebook for native AppUI MiniApps. Pair with pythonide-native whenever the workflow touches iOS permissions, sensors, media capture, networking side effects, or secure storage.
- appui quickstart
- AppUI module
- UI patterns
- AppUI schema and appui.pyi for signatures
- llms-full.txt when unsure about exported symbols
- Load
pythonide-native before importing any photos, location, permission, notification, etc.
Chinese module pages on pythonide.xin are valid deep references for scenarios and examples.
- Confirm AppUI is the right runtime (
pythonide router). Do not use AppUI for widgets or frame-loop games.
- Choose structure first:
Form, List, TabView, NavigationStack, dashboard ScrollView.
- Define module-level
appui.State or ReactiveState.
- Write named zero-argument callbacks; callbacks mutate state or call native APIs.
- Keep
body() pure: return a view tree only. No permissions, network, storage writes, notifications, timers started, or navigation side effects inside body().
- End with exactly one
appui.run(body, state=state, presentation=...).
- For editable business data, use
storage.get_json(..., default) only for small settings; use database for queryable records, caches, favorites, history, playlists, or large lists.
import appui
state = appui.State(count=0, status="Ready")
def increment():
state.count += 1
state.status = "Updated"
def body():
return appui.NavigationStack(
appui.Form([
appui.Section("Counter", [
appui.LabeledContent("Value", value=str(state.count)),
appui.Button("+1", action=increment).button_style("bordered_prominent"),
], footer=state.status),
]).navigation_title("Counter")
)
appui.run(body, state=state, presentation="fullscreen_with_close")
| Use case |
presentation |
| Full app / tool with close affordance |
fullscreen_with_close |
| Lightweight utility |
sheet |
| Default when unsure for a real app |
fullscreen_with_close |
- Settings, account, checkout, profile:
Form + Section
- Dynamic rows:
List + ForEach with stable key
- Master-detail:
NavigationStack + NavigationLink
- Product areas:
TabView + Tab, each tab owns a NavigationStack
- Continuing bottom tasks:
TabView + .tab_view_bottom_accessory(...) + .sheet(...)
- Use
.tab_view_bottom_accessory for the compact persistent row and .sheet(detents=..., drag_indicator=...) for the full panel. Do not invent an expandable bottom accessory API.
- Search: native
.searchable(...); do not hand-roll search bars
- Toolbar actions in fullscreen apps;
fullscreen_with_close already provides close
- Stable item IDs; mutate by id, never by filtered index
- Do not nest
List inside ScrollView, Section, or another scrolling container
- Target iPhone width ~390 pt; avoid fixed widths above 360 pt
- Padding usually 8–20 pt
- Use semantic system colors:
systemBackground, label, secondaryLabel, systemBlue, etc.
- Do not hard-code hex/RGB unless the API requires it
- Ordinary rows/settings/todos should stay
List/Form native-first; reserve custom dashboard layouts for true dashboard/media surfaces
- Use
.tab_view_bottom_accessory plus .sheet for persistent playback, podcast, recording, download, timer, or navigation state instead of making that ongoing task a dedicated tab
Also load pythonide-native for any capability below.
nativeCallsStayOutOfAppUIBody: permission prompts, GPS, capture, notifications, and network side effects belong in named callbacks, .refreshable, or .task — never in body()
- Prefer AppUI bridge components before imperative modules when the control is inline in the UI
- AppUI embedded video:
PlayerController + VideoPlayer; do not use import avplayer unless the user explicitly wants script-level playback
- Secrets →
keychain; small settings → storage; queryable records → database
- Do not import CPython
sqlite3 directly in MiniApps; use database
| Need |
Prefer |
| Inline photo pick in a form |
appui.PhotoPicker |
| Inline camera capture button |
appui.CameraPicker |
| Map preview in a screen |
appui.MapView + pythonide-native for location callbacks |
| Share a file or text |
appui.ShareLink |
| Batch asset processing, save to library, background fetch |
import photos in callback |
| GPS updates, geocoding, heading |
import location in callback |
| Local notifications |
import notification in callback |
- Prefer named zero-argument callbacks
- Wrong:
Button(action=open(item)) or row.on_tap(save(i)) — runs during view construction
- Row callbacks should capture stable item IDs
- Timers at module scope with
action= at construction; never Timer(action=None) then assign later
- High-frequency sensors, camera streams, waveforms → consider Aurora realtime docs; do not rebuild the whole tree every frame in a tight loop
- No HTML/WebView unless the user explicitly needs web content
- No
lambda callbacks in deliverable code
- No
children= for VStack/HStack; pass child lists positionally
- No
NavigationStack([...]); one root view only
- No
ScrollView(VStack(...)) for ordinary lists → use List(ForEach(...))
- No dedicated playback/download/recording tab when the expected product pattern is a compact bottom bar that expands into a full panel
- No
Image(...).on_tap(...) as a button → use Button
- No guessed SwiftUI-only APIs; verify against schema/stubs
- No tkinter, PyQt, Flask, Streamlit, or browser frameworks
| Symptom |
Fix |
| Dead button |
Named zero-arg callback instead of eager invocation |
| Wrong row after search/filter |
Stable ids + ForEach(..., key=...) |
| Permission dialog on launch |
Move native call into button callback |
| Blank preview |
Ensure body() returns a View and appui.run(...) is present |
| Shared search across tabs |
Separate per-tab query state |
examples/counter/ — minimal Form + persistence-ready structure
examples/photo_notes/ — AppUI shell + PhotoPicker + storage
Done means: runnable AppUI code, documented APIs only, appui.run(...) present, named callbacks for side effects, and honest reporting if editor-side preview cannot be executed.