Pythonide appui
Official Agent Skills for building PythonIDE AppUI MiniApps, widgets, automations, and native iOS integrations with Codex, Cursor, and Claude Code.
npx -y skills add Python-IDE/pythonide-skills --skill pythonide-appuiAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
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.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
8.4 KB, as published. Nobody here has run it
PythonIDE AppUI
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.
Read Before Coding
- appui quickstart
- AppUI module
- UI patterns
- AppUI schema and appui.pyi for signatures
- llms-full.txt when unsure about exported symbols
- Load
pythonide-nativebefore importing anyphotos,location,permission,notification, etc.
Chinese module pages on pythonide.xin are valid deep references for scenarios and examples.
Workflow
- Confirm AppUI is the right runtime (
pythoniderouter). Do not use AppUI for widgets or frame-loop games. - Choose structure first:
Form,List,TabView,NavigationStack, dashboardScrollView. - Define module-level
appui.StateorReactiveState. - 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 insidebody(). - End with exactly one
appui.run(body, state=state, presentation=...). - For editable business data, use
storage.get_json(..., default)only for small settings; usedatabasefor queryable records, caches, favorites, history, playlists, or large lists.
Entry Pattern
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")
Presentation
| 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 |
Structure Rules
- Settings, account, checkout, profile:
Form + Section - Dynamic rows:
List + ForEachwith stablekey - Master-detail:
NavigationStack + NavigationLink - Product areas:
TabView + Tab, each tab owns aNavigationStack - Continuing bottom tasks:
TabView + .tab_view_bottom_accessory(...) + .sheet(...) - Use
.tab_view_bottom_accessoryfor 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_closealready provides close - Stable item IDs; mutate by id, never by filtered index
- Do not nest
ListinsideScrollView,Section, or another scrolling container
Layout And Styling
- 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/Formnative-first; reserve custom dashboard layouts for true dashboard/media surfaces - Use
.tab_view_bottom_accessoryplus.sheetfor persistent playback, podcast, recording, download, timer, or navigation state instead of making that ongoing task a dedicated tab
Native Integration
Also load pythonide-native for any capability below.
Hard Rules
nativeCallsStayOutOfAppUIBody: permission prompts, GPS, capture, notifications, and network side effects belong in named callbacks,.refreshable, or.task— never inbody()- Prefer AppUI bridge components before imperative modules when the control is inline in the UI
- AppUI embedded video:
PlayerController+VideoPlayer; do not useimport avplayerunless the user explicitly wants script-level playback - Secrets →
keychain; small settings →storage; queryable records →database - Do not import CPython
sqlite3directly in MiniApps; usedatabase
AppUI Bridge Components
<!-- BEGIN GENERATED: appui-bridge-table -->| Bridge | AppUI component | Related modules | Doc |
|---|---|---|---|
camera_picker | appui.CameraPicker | photos | camera-module |
file_importer | appui.FileImporter | - | file-picker-module |
map_view | appui.MapView | location, permission | location-module |
photo_picker | appui.PhotoPicker | photos | photos-module |
player_controller | appui.PlayerController | avplayer | appui-ref-media |
share_link | appui.ShareLink | - | share-module |
video_player | appui.VideoPlayer | avplayer | appui-ref-media |
web_view | appui.WebView | - | appui-ref-media |
Bridge Vs Module
| 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 |
Callbacks And Runtime
- Prefer named zero-argument callbacks
- Wrong:
Button(action=open(item))orrow.on_tap(save(i))— runs during view construction - Row callbacks should capture stable item IDs
- Timers at module scope with
action=at construction; neverTimer(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
Hard Stops
- No HTML/WebView unless the user explicitly needs web content
- No
lambdacallbacks in deliverable code - No
children=forVStack/HStack; pass child lists positionally - No
NavigationStack([...]); one root view only - No
ScrollView(VStack(...))for ordinary lists → useList(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 → useButton - No guessed SwiftUI-only APIs; verify against schema/stubs
- No tkinter, PyQt, Flask, Streamlit, or browser frameworks
Common Mistakes
| 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
examples/counter/— minimalForm+ persistence-ready structureexamples/photo_notes/— AppUI shell +PhotoPicker+storage
Validation
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.