tempestroid 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. tempestroid-0.1.0/.gitignore +73 -0
  2. tempestroid-0.1.0/CHANGELOG.md +38 -0
  3. tempestroid-0.1.0/LICENSE +21 -0
  4. tempestroid-0.1.0/PKG-INFO +368 -0
  5. tempestroid-0.1.0/README.md +337 -0
  6. tempestroid-0.1.0/docs/arquitetura.en.md +95 -0
  7. tempestroid-0.1.0/docs/arquitetura.md +98 -0
  8. tempestroid-0.1.0/docs/guia/cli.en.md +58 -0
  9. tempestroid-0.1.0/docs/guia/cli.md +58 -0
  10. tempestroid-0.1.0/docs/guia/estilos.en.md +136 -0
  11. tempestroid-0.1.0/docs/guia/estilos.md +138 -0
  12. tempestroid-0.1.0/docs/guia/eventos.en.md +67 -0
  13. tempestroid-0.1.0/docs/guia/eventos.md +67 -0
  14. tempestroid-0.1.0/docs/guia/exemplos.en.md +47 -0
  15. tempestroid-0.1.0/docs/guia/exemplos.md +48 -0
  16. tempestroid-0.1.0/docs/guia/widgets.en.md +114 -0
  17. tempestroid-0.1.0/docs/guia/widgets.md +115 -0
  18. tempestroid-0.1.0/docs/index.en.md +52 -0
  19. tempestroid-0.1.0/docs/index.md +57 -0
  20. tempestroid-0.1.0/docs/inicio-rapido.en.md +85 -0
  21. tempestroid-0.1.0/docs/inicio-rapido.md +87 -0
  22. tempestroid-0.1.0/docs/instalacao.en.md +51 -0
  23. tempestroid-0.1.0/docs/instalacao.md +48 -0
  24. tempestroid-0.1.0/docs/plan.md +303 -0
  25. tempestroid-0.1.0/docs/referencia/api.en.md +74 -0
  26. tempestroid-0.1.0/docs/referencia/api.md +75 -0
  27. tempestroid-0.1.0/docs/referencia/dispositivo.en.md +64 -0
  28. tempestroid-0.1.0/docs/referencia/dispositivo.md +66 -0
  29. tempestroid-0.1.0/docs/research/android-runbook.md +187 -0
  30. tempestroid-0.1.0/docs/research/android-runtime.md +106 -0
  31. tempestroid-0.1.0/docs/roadmap.en.md +52 -0
  32. tempestroid-0.1.0/docs/roadmap.md +52 -0
  33. tempestroid-0.1.0/examples/README.md +65 -0
  34. tempestroid-0.1.0/examples/calculator/app.py +185 -0
  35. tempestroid-0.1.0/examples/colorpicker/app.py +169 -0
  36. tempestroid-0.1.0/examples/counter/app.py +115 -0
  37. tempestroid-0.1.0/examples/device_counter/app.py +47 -0
  38. tempestroid-0.1.0/examples/form/app.py +157 -0
  39. tempestroid-0.1.0/examples/gallery/app.py +195 -0
  40. tempestroid-0.1.0/examples/stopwatch/app.py +156 -0
  41. tempestroid-0.1.0/examples/todo/app.py +220 -0
  42. tempestroid-0.1.0/mkdocs.yml +126 -0
  43. tempestroid-0.1.0/pyproject.toml +120 -0
  44. tempestroid-0.1.0/tempestroid/__init__.py +198 -0
  45. tempestroid-0.1.0/tempestroid/bridge/__init__.py +35 -0
  46. tempestroid-0.1.0/tempestroid/bridge/device.py +148 -0
  47. tempestroid-0.1.0/tempestroid/bridge/handlers.py +103 -0
  48. tempestroid-0.1.0/tempestroid/bridge/jni.py +143 -0
  49. tempestroid-0.1.0/tempestroid/bridge/protocol.py +136 -0
  50. tempestroid-0.1.0/tempestroid/bridge/serializer.py +115 -0
  51. tempestroid-0.1.0/tempestroid/cli/__init__.py +27 -0
  52. tempestroid-0.1.0/tempestroid/cli/app_loader.py +114 -0
  53. tempestroid-0.1.0/tempestroid/cli/main.py +262 -0
  54. tempestroid-0.1.0/tempestroid/cli/packaging.py +212 -0
  55. tempestroid-0.1.0/tempestroid/cli/scaffold.py +178 -0
  56. tempestroid-0.1.0/tempestroid/cli/watcher.py +77 -0
  57. tempestroid-0.1.0/tempestroid/core/__init__.py +40 -0
  58. tempestroid-0.1.0/tempestroid/core/introspection.py +115 -0
  59. tempestroid-0.1.0/tempestroid/core/ir.py +131 -0
  60. tempestroid-0.1.0/tempestroid/core/reconciler.py +191 -0
  61. tempestroid-0.1.0/tempestroid/core/state.py +152 -0
  62. tempestroid-0.1.0/tempestroid/devserver/__init__.py +19 -0
  63. tempestroid-0.1.0/tempestroid/devserver/client.py +135 -0
  64. tempestroid-0.1.0/tempestroid/devserver/qr.py +34 -0
  65. tempestroid-0.1.0/tempestroid/devserver/server.py +149 -0
  66. tempestroid-0.1.0/tempestroid/native/__init__.py +11 -0
  67. tempestroid-0.1.0/tempestroid/native/dispatch.py +47 -0
  68. tempestroid-0.1.0/tempestroid/native/notifications.py +22 -0
  69. tempestroid-0.1.0/tempestroid/py.typed +0 -0
  70. tempestroid-0.1.0/tempestroid/renderers/__init__.py +9 -0
  71. tempestroid-0.1.0/tempestroid/renderers/compose/__init__.py +11 -0
  72. tempestroid-0.1.0/tempestroid/renderers/compose/style_translator.py +224 -0
  73. tempestroid-0.1.0/tempestroid/renderers/qt/__init__.py +20 -0
  74. tempestroid-0.1.0/tempestroid/renderers/qt/app_runner.py +61 -0
  75. tempestroid-0.1.0/tempestroid/renderers/qt/dev_loop.py +135 -0
  76. tempestroid-0.1.0/tempestroid/renderers/qt/renderer.py +1000 -0
  77. tempestroid-0.1.0/tempestroid/renderers/qt/simulator.py +85 -0
  78. tempestroid-0.1.0/tempestroid/renderers/qt/style_translator.py +224 -0
  79. tempestroid-0.1.0/tempestroid/style.py +466 -0
  80. tempestroid-0.1.0/tempestroid/widgets/__init__.py +82 -0
  81. tempestroid-0.1.0/tempestroid/widgets/base.py +170 -0
  82. tempestroid-0.1.0/tempestroid/widgets/button.py +25 -0
  83. tempestroid-0.1.0/tempestroid/widgets/events.py +151 -0
  84. tempestroid-0.1.0/tempestroid/widgets/indicators.py +39 -0
  85. tempestroid-0.1.0/tempestroid/widgets/inputs.py +193 -0
  86. tempestroid-0.1.0/tempestroid/widgets/layout.py +99 -0
  87. tempestroid-0.1.0/tempestroid/widgets/media.py +50 -0
  88. tempestroid-0.1.0/tempestroid/widgets/text.py +17 -0
@@ -0,0 +1,73 @@
1
+ # ─── Python ───────────────────────────────────────────────────────────────
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ *.egg
7
+ *.egg-info/
8
+ .eggs/
9
+ build/
10
+ dist/
11
+ wheels/
12
+ *.whl
13
+ .installed.cfg
14
+
15
+ # Virtual environments
16
+ .venv/
17
+ venv/
18
+ env/
19
+ ENV/
20
+
21
+ # Tooling caches
22
+ .pytest_cache/
23
+ .ruff_cache/
24
+ .mypy_cache/
25
+ .pyright/
26
+ .coverage
27
+ .coverage.*
28
+ htmlcov/
29
+ .tox/
30
+ .nox/
31
+ .cache/
32
+
33
+ # ─── Editors / OS ─────────────────────────────────────────────────────────
34
+ .vscode/
35
+ .idea/
36
+ *.swp
37
+ *~
38
+ .DS_Store
39
+ Thumbs.db
40
+
41
+ # ─── Environment / secrets ─────────────────────────────────────────────────
42
+ .env
43
+ .env.*
44
+ !.env.example
45
+
46
+ # ─── Trilho B: toolchain (regenerable build outputs of B0/B1) ───────────────
47
+ # Built CPython, native wheels, staged site-packages and cloned sources.
48
+ # Reproduce with toolchain/00_fetch_cpython.sh, 01_build_wheels.sh, 02_stage_deps.sh.
49
+ toolchain/dist/
50
+ toolchain/.src/
51
+ toolchain/wheelhouse/
52
+
53
+ # ─── Trilho B: android-host (Gradle / NDK build artifacts) ──────────────────
54
+ android-host/build/
55
+ android-host/app/build/
56
+ android-host/app/.cxx/
57
+ android-host/.gradle/
58
+ android-host/.kotlin/
59
+ android-host/local.properties
60
+ android-host/captures/
61
+ *.apk
62
+ *.aab
63
+ *.ap_
64
+ *.dex
65
+
66
+ # Keep the Gradle wrapper jar tracked (do not ignore it):
67
+ !android-host/gradle/wrapper/gradle-wrapper.jar
68
+
69
+ # MkDocs build output
70
+ site/
71
+
72
+ # `tempest build` embeds the user app here as a generated asset (phase C).
73
+ android-host/app/src/main/assets/tempest_app.py
@@ -0,0 +1,38 @@
1
+ # Changelog
2
+
3
+ All notable changes to **tempestroid** are documented here. The format is based
4
+ on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] — 2026-05-31
10
+
11
+ First public release. The framework (Trilho A) is complete and the Android
12
+ runtime (Trilho B, phases B0–B6) is validated on a real arm64 device.
13
+
14
+ ### Added
15
+
16
+ - **Typed declarative UI core.** A frozen-Pydantic `Style` model (`Color`,
17
+ `Edge`, `Border`, `Transition`, and the style enums) and a widget IR
18
+ (`Widget`, `Text`, `Button`, `Column`, `Row`, `Container`, plus value-bearing
19
+ inputs and utility widgets).
20
+ - **Renderer-agnostic reconciler** — `build → diff → patch` (`Insert` / `Remove`
21
+ / `Update` / `Reorder` / `Replace`).
22
+ - **Qt desktop simulator** (`run_qt`, optional `qt` extra) with a `Style → Qt`
23
+ translator and an asyncio×Qt event loop via `qasync`.
24
+ - **Async-first state runtime** (`App`) with coalesced rebuilds and **stateful
25
+ hot reload** (`App.swap_view`).
26
+ - **`tempest` CLI** — `dev` (simulator + hot reload/restart cockpit), `serve`
27
+ (LAN code-push to a device), `spec` (typed contract as JSON), `new` (scaffold
28
+ a project), `build` (bundle an app into an APK), `run` (build + install +
29
+ logcat).
30
+ - **Android runtime (Trilho B).** Official CPython 3.14 (PEP 738) embedded via a
31
+ hand-rolled JNI bridge in a Kotlin/Gradle host; native wheels (`pydantic-core`)
32
+ via cibuildwheel; a Jetpack Compose renderer; a LAN dev server with QR pairing;
33
+ and native capabilities (notifications).
34
+ - **Conformance suite** pinning the Qt and Compose `Style` translators with
35
+ golden snapshots (phase D).
36
+
37
+ [Unreleased]: https://github.com/mauriciobenjamin700/tempestroid/compare/v0.1.0...HEAD
38
+ [0.1.0]: https://github.com/mauriciobenjamin700/tempestroid/releases/tag/v0.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mauricio Benjamin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,368 @@
1
+ Metadata-Version: 2.4
2
+ Name: tempestroid
3
+ Version: 0.1.0
4
+ Summary: Framework for building native Android apps in typed Python — declarative typed UI tree, Qt simulator + Compose device renderers.
5
+ Project-URL: Homepage, https://github.com/mauriciobenjamin700/tempestroid
6
+ Project-URL: Documentation, https://mauriciobenjamin700.github.io/tempestroid/
7
+ Project-URL: Repository, https://github.com/mauriciobenjamin700/tempestroid
8
+ Project-URL: Changelog, https://github.com/mauriciobenjamin700/tempestroid/blob/main/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/mauriciobenjamin700/tempestroid/issues
10
+ Author: Mauricio Benjamin
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: android,compose,cross-platform,declarative,framework,mobile,pydantic,qt,ui
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: Android
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
23
+ Classifier: Topic :: Software Development :: User Interfaces
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.11
26
+ Requires-Dist: pydantic>=2.7
27
+ Provides-Extra: qt
28
+ Requires-Dist: pyside6>=6.7; extra == 'qt'
29
+ Requires-Dist: qasync>=0.27; extra == 'qt'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # tempestroid
33
+
34
+ > 📖 **Documentação / Docs:** site MkDocs bilíngue em [`docs/`](docs/index.md)
35
+ > com seletor **PT-BR / EN-US** no header — rode `uv run mkdocs serve` e abra
36
+ > <http://127.0.0.1:8000> (PT) ou <http://127.0.0.1:8000/en/> (EN). Guia do
37
+ > usuário, arquitetura e referência da API.
38
+
39
+ Build **native Android apps** in **typed Python**.
40
+
41
+ You write one declarative, fully typed widget tree (a Pydantic IR). A
42
+ **renderer-agnostic reconciler** diffs it into patches. Two leaf renderers apply
43
+ those patches: **Qt** for the desktop simulator, **Jetpack Compose** for the
44
+ device. The runtime is **async-first**, with an Expo-style dev loop: hot reload
45
+ in the Qt simulator and LAN code-push to a device over QR — both shipping today.
46
+
47
+ > This is a **framework, not a web service** — no FastAPI, SQLAlchemy, Redis, or
48
+ > HTTP layering. See [`docs/plan.md`](docs/plan.md) for the full design and the
49
+ > phase roadmap.
50
+
51
+ ---
52
+
53
+ ## Why
54
+
55
+ - **Typed end to end.** Style model, widget primitives, events, and the
56
+ Python↔Kotlin boundary contract are all Pydantic v2 / fully typed. `pyright`
57
+ runs in strict mode.
58
+ - **One tree, two targets.** The reconciler is pure data-in → patches-out. All
59
+ platform divergence is confined to the two `Style` translators (Qt today,
60
+ Compose next).
61
+ - **Async-first.** Event handlers and lifecycle hooks may be sync or `async`;
62
+ Python runs on a background asyncio loop, never the UI thread.
63
+ - **Fast inner loop.** `tempest dev` watches your file and hot-restarts the Qt
64
+ simulator on save — no device or emulator needed for UI work.
65
+
66
+ ---
67
+
68
+ ## How it works
69
+
70
+ ```text
71
+ view(app) ──build──▶ Node tree (IR)
72
+
73
+ diff pure, renderer-agnostic
74
+
75
+ [ Patch ] Insert / Remove / Update / Reorder / Replace
76
+ ╱ ╲
77
+ Qt renderer Compose renderer
78
+ (simulator) (device, B4)
79
+ ```
80
+
81
+ 1. `view(app) -> Widget` builds a declarative widget tree from current state.
82
+ 2. `build` lowers it to a `Node` IR; `diff` compares old vs. new and emits a
83
+ minimal `Patch` list.
84
+ 3. A renderer applies patches to live widgets. State changes coalesce into one
85
+ rebuild per tick.
86
+
87
+ ---
88
+
89
+ ## Install
90
+
91
+ ```bash
92
+ uv sync # core + dev tooling + the Qt simulator
93
+ ```
94
+
95
+ End users embedding the framework who want the simulator:
96
+ `pip install tempestroid[qt]`. The framework **core** needs only `pydantic` —
97
+ Qt is an optional extra.
98
+
99
+ ---
100
+
101
+ ## Quick start
102
+
103
+ ```python
104
+ from dataclasses import dataclass
105
+
106
+ from tempestroid import App, Button, Column, Style, Text, Widget
107
+ from tempestroid.renderers.qt import run_qt
108
+
109
+
110
+ @dataclass
111
+ class CounterState:
112
+ value: int = 0
113
+
114
+
115
+ def make_state() -> CounterState:
116
+ return CounterState()
117
+
118
+
119
+ def view(app: App[CounterState]) -> Widget:
120
+ def increment() -> None:
121
+ app.set_state(lambda s: setattr(s, "value", s.value + 1))
122
+
123
+ return Column(
124
+ style=Style(gap=8.0),
125
+ children=[
126
+ Text(content=f"Count: {app.state.value}", key="label"),
127
+ Button(label="+", on_click=increment, key="inc"),
128
+ ],
129
+ )
130
+
131
+
132
+ if __name__ == "__main__":
133
+ raise SystemExit(run_qt(make_state(), view, title="counter"))
134
+ ```
135
+
136
+ Full example with sync **and** `async` handlers:
137
+ [`examples/counter/app.py`](examples/counter/app.py).
138
+
139
+ ---
140
+
141
+ ## Gallery
142
+
143
+ A set of runnable example apps lives in [`examples/`](examples/README.md). Each
144
+ exposes the same `make_state()` + `view(app)` contract, so it runs in the Qt
145
+ simulator (`uv run python examples/<name>/app.py`) **and** on a device via
146
+ code-push (`uv run tempest serve examples/<name>/app.py`) with no changes.
147
+
148
+ | App | What it shows |
149
+ |---|---|
150
+ | [`counter`](examples/counter/app.py) | Sync + `async` handlers, the basics. |
151
+ | [`todo`](examples/todo/app.py) | Type-to-add list — `Input` + `insert` / `remove` / `update` patches. |
152
+ | [`calculator`](examples/calculator/app.py) | Dense nested `Row`/`Column` button grid. |
153
+ | [`stopwatch`](examples/stopwatch/app.py) | Async loop ticking the UI via `asyncio.sleep`. |
154
+ | [`colorpicker`](examples/colorpicker/app.py) | Dynamic `Style` updates (swatches + toggles). |
155
+ | [`form`](examples/form/app.py) | The value-bearing inputs (`Input` / `Checkbox` / `DatePicker` / `FilePicker`) + their typed change events. |
156
+ | [`gallery`](examples/gallery/app.py) | The expanded set — `Slider` / `Switch` / `ProgressBar` / `Spinner` / `Image` / `Icon` / `ScrollView`, secure + regex + multiline text fields, and a `Style.transition`. |
157
+
158
+ The framework and the Qt simulator support the full widget set, including the
159
+ value-bearing inputs and the utility widgets (`Slider` / `Switch` /
160
+ `ProgressBar` / `Spinner` / `Image` / `Icon` / `ScrollView` / `TextArea`). The
161
+ device (Compose) renderer renders `Text` / `Button` / `Column` / `Row` /
162
+ `Container` plus the value widgets `Input` / `Checkbox` / `DatePicker` /
163
+ `FilePicker` (with their typed change events); the remaining utility widgets stay
164
+ empty-box on device until the Kotlin host grows the matching cases (see
165
+ [`examples/README.md`](examples/README.md)).
166
+
167
+ ---
168
+
169
+ ## CLI
170
+
171
+ ```bash
172
+ uv run tempest new MyApp # scaffold a new app project
173
+ uv run python examples/counter/app.py # run an app directly in the Qt simulator
174
+ uv run tempest dev examples/counter/app.py # dev loop: edit + save → hot reload (state preserved)
175
+ uv run tempest serve examples/device_counter/app.py # push to a device over LAN, no APK rebuild
176
+ uv run tempest build MyApp/app.py # bundle the app into an APK
177
+ uv run tempest run MyApp/app.py # build + install on a device + stream logs
178
+ uv run tempest spec # print the typed contract (widgets/events) as JSON
179
+ uv run tempest --help
180
+ ```
181
+
182
+ `tempest dev` cockpit commands: `r` (hot reload, state preserved), `R` (hot
183
+ restart, clean state), `s` (raise window), `q` (quit). Saving the file
184
+ hot-reloads; a reload incompatible with the live state falls back to a clean
185
+ restart. `tempest build`/`run` drive the `android-host` Gradle project + `adb`,
186
+ so they need an Android SDK/NDK and a checkout of the host tree.
187
+
188
+ | Command | Status | Notes |
189
+ |---|---|---|
190
+ | `tempest new <name>` | ✅ | Scaffold a runnable app project |
191
+ | `tempest dev <app>` | ✅ | Simulator + hot reload / hot restart (needs `qt` extra) |
192
+ | `tempest serve <app>` | ✅ | LAN code-push to a device + log relay (phase B5) |
193
+ | `tempest spec` | ✅ | Typed widget/event contract as JSON |
194
+ | `tempest build <app>` | ✅ | Bundle an app into an APK (needs Android SDK/NDK) |
195
+ | `tempest run <app>` | ✅ | Build + install on a device + stream logs |
196
+
197
+ ---
198
+
199
+ ## Public API
200
+
201
+ Everything below is importable from the top-level `tempestroid` package.
202
+
203
+ ### Style (`tempestroid.style`)
204
+
205
+ Frozen Pydantic value objects, diffed by value.
206
+
207
+ - **`Style`** — the style model (layout, box model, paint, typography, sizing,
208
+ effects, animation). Notable fields: `opacity`, `shadow`, `align_self`,
209
+ `letter_spacing`, `line_height`, `max_lines`, `text_overflow`, `aspect_ratio`.
210
+ - **`Color`** — `Color.from_hex("#101418")`.
211
+ - **`Edge`** — insets; `Edge.all(24.0)`.
212
+ - **`Border`** (uniform) / **`SideBorder`** (per-side, e.g. a bottom divider).
213
+ - **`Corners`** — per-corner radii for `Style.radius` (e.g. top-rounded sheets).
214
+ - **`Shadow`** — `box-shadow` / elevation (`color` / `blur` / `offset_x` /
215
+ `offset_y`); Compose maps it to elevation, Qt to a `QGraphicsDropShadowEffect`.
216
+ - **`Gradient`** + **`GradientStop`** — a linear gradient usable wherever a
217
+ background `Color` is (QSS `qlineargradient` / Compose `Brush`).
218
+ - **`Transition`** — implicit animation (`duration_ms` / `curve` / `delay_ms`):
219
+ on rebuild the renderer tweens changed visual props instead of snapping
220
+ (Compose maps it to `animate*AsState`; Qt animation is renderer-imperative).
221
+ - Enums: **`FlexDirection`**, **`JustifyContent`**, **`AlignItems`**,
222
+ **`TextAlign`**, **`FontWeight`**, **`FontStyle`**, **`TextDecoration`**,
223
+ **`TextOverflow`**, **`GradientDirection`**, **`Curve`** (easing).
224
+
225
+ ### Widgets (`tempestroid.widgets`)
226
+
227
+ The declarative IR — bare-noun widgets.
228
+
229
+ - **`Widget`** (base), **`Text`**, **`Button`**, **`Column`**, **`Row`**,
230
+ **`Container`**, **`ScrollView`** (scrollable container).
231
+ - Value-bearing inputs: **`Input`** (text — with `secure` password masking +
232
+ reveal toggle, regex `pattern`, `keyboard` type, `max_length`), **`TextArea`**
233
+ (multi-line), **`Checkbox`** (boolean), **`Switch`** (boolean toggle),
234
+ **`Slider`** (numeric range), **`DatePicker`** (ISO date), **`FilePicker`**
235
+ (file selection).
236
+ - Presentation widgets: **`Image`** (URL/asset, `fit`), **`Icon`** (named glyph),
237
+ **`ProgressBar`** (determinate/indeterminate), **`Spinner`** (activity).
238
+ - Enums: **`KeyboardType`** (text/number/email/phone/url/password),
239
+ **`ImageFit`** (contain/cover/fill/none).
240
+ - **`EventHandler`** — the typed handler-prop wrapper used by every handler field
241
+ (`on_click`, `on_change`, `on_select`); sync or `async`, zero- or one-argument.
242
+
243
+ ### Events (`tempestroid.widgets`) — typed boundary contract
244
+
245
+ - **`Event`** (base), **`TapEvent`**, **`TextChangeEvent`** (carries `valid`
246
+ against the input's `pattern`), **`ToggleEvent`**, **`SlideEvent`**,
247
+ **`DateChangeEvent`**, **`FileSelectEvent`**.
248
+ - **`parse_event(event_type, raw)`** — boundary gate: validates a raw payload
249
+ into a typed event or raises **`EventValidationError`** with structured field
250
+ errors. This is the Python↔Kotlin contract for the device bridge. The bridge
251
+ passes the validated event to handlers that accept a positional argument.
252
+
253
+ ### Core — IR + reconciler (`tempestroid.core`)
254
+
255
+ - **`Node`**, **`Path`** — the lowered IR.
256
+ - Patches: **`Insert`**, **`Remove`**, **`Update`**, **`Reorder`**,
257
+ **`Replace`**, and the **`Patch`** union.
258
+ - **`build(widget) -> Node`**, **`diff(old, new) -> list[Patch]`**.
259
+ - **`App[S]`** — renderer-agnostic state container: owns state, builds via
260
+ `view(app)`, diffs, hands patches to an `apply_patches` callback.
261
+
262
+ ### Introspection (`tempestroid.core`)
263
+
264
+ - **`introspect()`** — full JSON contract `{"widgets": {...}, "events": {...}}`
265
+ (powers `tempest spec`).
266
+ - **`widget_catalog()`**, **`event_catalog()`**.
267
+
268
+ ### Renderer (`tempestroid.renderers.qt`, needs `qt` extra)
269
+
270
+ - **`run_qt(state, view, *, title, size)`** — run an app in the Qt simulator.
271
+ - **`run_dev(app_path)`** — the `tempest dev` cockpit.
272
+
273
+ ### Compose + bridge — device side (phases B3/B4)
274
+
275
+ The Python half is device-independent and tested without a phone; the JNI
276
+ transport (B3) and the Kotlin Compose renderer (B4) are implemented in
277
+ `android-host/` and verified on a real arm64 device.
278
+
279
+ - **`to_compose(style)`** (`tempestroid.renderers.compose`) — serializable
280
+ `Style → Compose` spec; the second `Style` translator (pairs with `Style → Qt`).
281
+ - **`serialize_node` / `serialize_patch`** — lower the IR/patches to JSON-able
282
+ dicts (handlers → path tokens, style → Compose spec).
283
+ - **`MountMessage` / `PatchMessage` / `EventMessage`** — the wire protocol across
284
+ the bridge: `mount` carries the full serialized tree, `patch` an incremental
285
+ patch list, `event` a device→Python callback addressed by handler token.
286
+ - **`DeviceApp`** + **`Bridge`** / **`LoopbackBridge`** — wire an `App` to a
287
+ device transport; the device-side analogue of `run_qt`. Events come back by
288
+ handler token, are validated by `parse_event`, and trigger coalesced patches.
289
+ - **`JniBridge`** + **`run_device`** — the real on-device transport (phase B3):
290
+ `JniBridge` ships messages to Kotlin via the native `_tempest_host` module;
291
+ `run_device(state, view)` boots a `DeviceApp` on a fresh asyncio loop and
292
+ marshals incoming events back onto it. Imports cleanly off-device (the native
293
+ module is loaded lazily), so the framework still develops/tests on the desktop.
294
+
295
+ ### Dev server — LAN code-push (phase B5)
296
+
297
+ The Expo-style on-device inner loop: edit on the dev machine, hot-restart on the
298
+ phone without rebuilding the APK (`tempest serve <app>`).
299
+
300
+ - **`DevServer`** — serves the app source (`/version`, `/app`) and relays device
301
+ logs (`/log`) over HTTP.
302
+ - **`run_dev_client`** — the device poll loop: fetch on change → re-exec source →
303
+ hot-restart the `DeviceApp` (transport/fetch injected, so it's desktop-testable).
304
+ - **`serve_device(url)`** — device entry point wiring the real `JniBridge` + the
305
+ native sink + an `urllib` fetch into `run_dev_client`.
306
+ - **`render_qr(url)`** — ASCII QR for pairing (falls back to the plain URL).
307
+
308
+ ### Native capabilities (phase B6)
309
+
310
+ Device-native features driven from Python as `{"kind": "native"}` commands the
311
+ Kotlin host routes to capability modules. Verified on device.
312
+
313
+ - **`notify(title, body="")`** — post a system notification from a handler.
314
+ The extension pattern (`native_command` envelope + a host module router) is in
315
+ place for further capabilities (camera, sensors, …).
316
+
317
+ ---
318
+
319
+ ## Project layout
320
+
321
+ ```text
322
+ tempestroid/
323
+ ├── style.py # Style + value objects (Color/Edge/Border/Corners/Shadow/Gradient/Transition) + enums (frozen Pydantic)
324
+ ├── widgets/ # Widget base + layout/inputs/media/indicators widgets + events.py
325
+ ├── core/ # ir.py, reconciler.py, state.py, introspection.py
326
+ ├── renderers/qt/ # renderer, Style→Qt, run_qt, simulator, dev_loop
327
+ ├── renderers/compose/ # Style→Compose translator (device renderer, Python side)
328
+ ├── bridge/ # IR/patch serialization, handler registry, DeviceApp
329
+ └── cli/ # tempest entry point + app_loader + watcher
330
+
331
+ # Trilho B (Android), outside the Python package:
332
+ docs/research/ # web research + executable B0–B6 runbook
333
+ toolchain/ # fetch CPython 3.14 + cibuildwheel native wheels
334
+ android-host/ # Gradle/Kotlin host embedding official CPython via JNI
335
+ ```
336
+
337
+ ---
338
+
339
+ ## Status
340
+
341
+ Track A (pure desktop CPython) is **complete: A0–A6**.
342
+
343
+ | Phase | Scope | Status |
344
+ |---|---|---|
345
+ | A0 | Foundation: package, tooling, `tempest --help` | ✅ |
346
+ | A1 | Style model + typed widget primitives | ✅ |
347
+ | A2 | Reconciler: `build → diff → patch` | ✅ |
348
+ | A3 | Qt renderer: patches → `QWidget`s, `Style → Qt` | ✅ |
349
+ | A4 | Async event loop: asyncio ⨉ Qt (`qasync`) | ✅ |
350
+ | A5 | `tempest dev`: watcher, hot restart, command loop | ✅ |
351
+ | A6 | Typed event contract + introspection | ✅ |
352
+ | B0–B6 | Android runtime: CPython 3.14 arm64, native wheels, Kotlin host, JNI bridge, Compose renderer, LAN code-push, native capabilities | ✅ |
353
+ | C | Polish: `new`/`build`/`run` + stateful hot reload | ✅ |
354
+ | D | Conformance golden snapshots (Qt vs Compose) | ✅ |
355
+
356
+ ---
357
+
358
+ ## Develop
359
+
360
+ ```bash
361
+ uv run ruff check .
362
+ uv run pyright # strict mode
363
+ uv run pytest
364
+ ```
365
+
366
+ Conventions: double quotes everywhere, every parameter/return/annotation typed,
367
+ Google-style English docstrings, absolute imports re-exported from each
368
+ `__init__.py`. See [`CLAUDE.md`](CLAUDE.md) for the full set.