tempest-core 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 (67) hide show
  1. tempest_core-0.1.0/.github/workflows/ci.yml +26 -0
  2. tempest_core-0.1.0/.github/workflows/docs.yml +59 -0
  3. tempest_core-0.1.0/.github/workflows/publish.yml +43 -0
  4. tempest_core-0.1.0/.gitignore +8 -0
  5. tempest_core-0.1.0/CHANGELOG.md +34 -0
  6. tempest_core-0.1.0/PKG-INFO +66 -0
  7. tempest_core-0.1.0/README.md +47 -0
  8. tempest_core-0.1.0/docs/index.en.md +38 -0
  9. tempest_core-0.1.0/docs/index.md +38 -0
  10. tempest_core-0.1.0/docs/installation.en.md +32 -0
  11. tempest_core-0.1.0/docs/installation.md +32 -0
  12. tempest_core-0.1.0/docs/reference.en.md +9 -0
  13. tempest_core-0.1.0/docs/reference.md +8 -0
  14. tempest_core-0.1.0/docs/tutorial/index.en.md +54 -0
  15. tempest_core-0.1.0/docs/tutorial/index.md +53 -0
  16. tempest_core-0.1.0/docs/tutorial/state.en.md +52 -0
  17. tempest_core-0.1.0/docs/tutorial/state.md +51 -0
  18. tempest_core-0.1.0/docs/tutorial/style.en.md +49 -0
  19. tempest_core-0.1.0/docs/tutorial/style.md +49 -0
  20. tempest_core-0.1.0/mkdocs.yml +114 -0
  21. tempest_core-0.1.0/pyproject.toml +61 -0
  22. tempest_core-0.1.0/tempest_core/__init__.py +86 -0
  23. tempest_core-0.1.0/tempest_core/animation.py +425 -0
  24. tempest_core-0.1.0/tempest_core/components/__init__.py +119 -0
  25. tempest_core-0.1.0/tempest_core/components/bars.py +266 -0
  26. tempest_core-0.1.0/tempest_core/components/base.py +54 -0
  27. tempest_core-0.1.0/tempest_core/components/brforms.py +461 -0
  28. tempest_core-0.1.0/tempest_core/components/cards.py +200 -0
  29. tempest_core-0.1.0/tempest_core/components/dates.py +221 -0
  30. tempest_core-0.1.0/tempest_core/components/disclosure.py +83 -0
  31. tempest_core-0.1.0/tempest_core/components/feedback.py +194 -0
  32. tempest_core-0.1.0/tempest_core/components/fields.py +204 -0
  33. tempest_core-0.1.0/tempest_core/components/layout.py +179 -0
  34. tempest_core-0.1.0/tempest_core/components/mediainputs.py +254 -0
  35. tempest_core-0.1.0/tempest_core/components/menu.py +111 -0
  36. tempest_core-0.1.0/tempest_core/components/navigation.py +215 -0
  37. tempest_core-0.1.0/tempest_core/components/selection.py +269 -0
  38. tempest_core-0.1.0/tempest_core/components/table.py +235 -0
  39. tempest_core-0.1.0/tempest_core/core/__init__.py +45 -0
  40. tempest_core-0.1.0/tempest_core/core/introspection.py +271 -0
  41. tempest_core-0.1.0/tempest_core/core/ir.py +159 -0
  42. tempest_core-0.1.0/tempest_core/core/reconciler.py +302 -0
  43. tempest_core-0.1.0/tempest_core/core/state.py +656 -0
  44. tempest_core-0.1.0/tempest_core/devices.py +144 -0
  45. tempest_core-0.1.0/tempest_core/i18n.py +90 -0
  46. tempest_core-0.1.0/tempest_core/icons.py +394 -0
  47. tempest_core-0.1.0/tempest_core/navigation.py +114 -0
  48. tempest_core-0.1.0/tempest_core/py.typed +0 -0
  49. tempest_core-0.1.0/tempest_core/style.py +722 -0
  50. tempest_core-0.1.0/tempest_core/theme.py +126 -0
  51. tempest_core-0.1.0/tempest_core/validators.py +168 -0
  52. tempest_core-0.1.0/tempest_core/widgets/__init__.py +352 -0
  53. tempest_core-0.1.0/tempest_core/widgets/animated.py +283 -0
  54. tempest_core-0.1.0/tempest_core/widgets/base.py +386 -0
  55. tempest_core-0.1.0/tempest_core/widgets/button.py +31 -0
  56. tempest_core-0.1.0/tempest_core/widgets/events.py +760 -0
  57. tempest_core-0.1.0/tempest_core/widgets/forms.py +241 -0
  58. tempest_core-0.1.0/tempest_core/widgets/gestures.py +426 -0
  59. tempest_core-0.1.0/tempest_core/widgets/indicators.py +53 -0
  60. tempest_core-0.1.0/tempest_core/widgets/inputs.py +553 -0
  61. tempest_core-0.1.0/tempest_core/widgets/layout.py +370 -0
  62. tempest_core-0.1.0/tempest_core/widgets/lists.py +518 -0
  63. tempest_core-0.1.0/tempest_core/widgets/media.py +613 -0
  64. tempest_core-0.1.0/tempest_core/widgets/navigation_widgets.py +182 -0
  65. tempest_core-0.1.0/tempest_core/widgets/overlays.py +270 -0
  66. tempest_core-0.1.0/tempest_core/widgets/text.py +19 -0
  67. tempest_core-0.1.0/tests/test_core.py +33 -0
@@ -0,0 +1,26 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["**"]
6
+ pull_request:
7
+
8
+ jobs:
9
+ gate:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - name: Install uv
14
+ uses: astral-sh/setup-uv@v3
15
+ - name: Set up Python
16
+ run: uv python install 3.11
17
+ - name: Install
18
+ run: uv pip install --system -e ".[dev]"
19
+ - name: Lint
20
+ run: |
21
+ ruff check .
22
+ ruff format --check .
23
+ - name: Type-check
24
+ run: mypy --strict tempest_core
25
+ - name: Test
26
+ run: pytest -q
@@ -0,0 +1,59 @@
1
+ name: Docs
2
+
3
+ # Build the bilingual MkDocs site (PT-BR default + EN-US under /en/) and deploy
4
+ # it to GitHub Pages. The strict build is the gate — any warning fails the job.
5
+
6
+ on:
7
+ push:
8
+ branches: ["main"]
9
+ paths:
10
+ - "docs/**"
11
+ - "mkdocs.yml"
12
+ - "pyproject.toml"
13
+ - ".github/workflows/docs.yml"
14
+ pull_request:
15
+ paths:
16
+ - "docs/**"
17
+ - "mkdocs.yml"
18
+ - "pyproject.toml"
19
+ - ".github/workflows/docs.yml"
20
+ workflow_dispatch:
21
+
22
+ concurrency:
23
+ group: "pages"
24
+ cancel-in-progress: false
25
+
26
+ permissions:
27
+ contents: read
28
+ pages: write
29
+ id-token: write
30
+
31
+ jobs:
32
+ build:
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+ - name: Install uv
37
+ uses: astral-sh/setup-uv@v3
38
+ - name: Set up Python
39
+ run: uv python install 3.11
40
+ - name: Install docs dependencies
41
+ run: uv pip install --system -e ".[docs]"
42
+ - name: Build (strict — the gate)
43
+ run: uv run mkdocs build --strict
44
+ - name: Upload Pages artifact
45
+ uses: actions/upload-pages-artifact@v3
46
+ with:
47
+ path: site
48
+
49
+ deploy:
50
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
51
+ needs: build
52
+ runs-on: ubuntu-latest
53
+ environment:
54
+ name: github-pages
55
+ url: ${{ steps.deployment.outputs.page_url }}
56
+ steps:
57
+ - name: Deploy to GitHub Pages
58
+ id: deployment
59
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,43 @@
1
+ name: Publish
2
+
3
+ # Build the sdist + wheel and upload to PyPI when a version tag is pushed.
4
+ # Uses PyPI Trusted Publishing (OIDC) — no API token stored in the repo.
5
+ # Configure the publisher once at https://pypi.org/manage/account/publishing/
6
+ # (project: tempest-core, workflow: publish.yml, environment: pypi).
7
+
8
+ on:
9
+ push:
10
+ tags: ["v*"]
11
+ workflow_dispatch:
12
+
13
+ jobs:
14
+ build:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - name: Install uv
19
+ uses: astral-sh/setup-uv@v3
20
+ - name: Set up Python
21
+ run: uv python install 3.11
22
+ - name: Build sdist + wheel
23
+ run: uv build
24
+ - name: Check metadata
25
+ run: uvx twine check dist/*
26
+ - uses: actions/upload-artifact@v4
27
+ with:
28
+ name: dist
29
+ path: dist/
30
+
31
+ publish:
32
+ needs: build
33
+ runs-on: ubuntu-latest
34
+ environment: pypi
35
+ permissions:
36
+ id-token: write # required for Trusted Publishing (OIDC)
37
+ steps:
38
+ - uses: actions/download-artifact@v4
39
+ with:
40
+ name: dist
41
+ path: dist/
42
+ - name: Publish to PyPI
43
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ *.egg-info/
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+ site/
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to **tempest-core** are documented here. Format follows
4
+ [Keep a Changelog](https://keepachangelog.com/); this project adheres to semantic
5
+ versioning.
6
+
7
+ ## [0.1.0] — 2026-06-11
8
+
9
+ First public release. The renderer-agnostic UI core shared across the tempest
10
+ stack — extracted so consumers depend on a published package instead of vendoring
11
+ a copy.
12
+
13
+ ### Added
14
+
15
+ - **IR + reconciler**: `build` / `diff` (and `build_scene` / `diff_scene` for the
16
+ overlay layer), the `Node` / `Patch` model, and `App` state with a coalesced
17
+ rebuild loop.
18
+ - **Typed style model**: `Style`, `Color`, `Edge`, gradients, shadows, borders,
19
+ and transitions — no CSS cascade, inline-typed.
20
+ - **Widgets & components**: layout (Column/Row/Container/Stack), text, button,
21
+ inputs, checkbox, lists (LazyColumn/Row/Grid), overlays, gestures, media, plus
22
+ the composed component set (cards, forms, fields, tables, BR inputs, …).
23
+ - **Cross-cutting helpers**: animation, i18n (`translate`), navigation
24
+ (`Route`/`NavStack`/`routes_from_path`), theme, validators (CPF/CNPJ/email/
25
+ phone), icons, devices.
26
+ - No platform-coupled code (no Qt, JNI, Android, or DOM) — imports cleanly under
27
+ CPython, Pyodide and a headless server. Only hard dependency: `pydantic>=2`.
28
+
29
+ ### Notes
30
+
31
+ - Gate mirrors tempestroid: ruff (E/F/I/UP/B/Q/ANN/D + google docstrings),
32
+ pyright strict, pytest — all green.
33
+ - Consumed by [`tempestweb`](https://pypi.org/project/tempestweb/); tempestroid's
34
+ own migration onto this package is in progress.
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: tempest-core
3
+ Version: 0.1.0
4
+ Summary: Renderer-agnostic UI core (IR, reconciler, state, style, widgets) shared by tempestroid and tempestweb.
5
+ Author-email: Mauricio Benjamin <mauricio.benjamin@reloverelations.com>
6
+ License: MIT
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: pydantic>=2.0
9
+ Provides-Extra: dev
10
+ Requires-Dist: mypy>=1.10; extra == 'dev'
11
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
12
+ Requires-Dist: pytest>=8; extra == 'dev'
13
+ Requires-Dist: ruff>=0.6; extra == 'dev'
14
+ Provides-Extra: docs
15
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
16
+ Requires-Dist: mkdocs-static-i18n>=1.2; extra == 'docs'
17
+ Requires-Dist: mkdocstrings[python]>=0.25; extra == 'docs'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # tempest-core
21
+
22
+ 📚 **Documentation:** [Português (Brasil)](https://mauriciobenjamin700.github.io/tempest-core/)
23
+ · [English (US)](https://mauriciobenjamin700.github.io/tempest-core/en/) — bilingual, on GitHub Pages.
24
+
25
+ **Renderer-agnostic UI core** shared across the tempest stack — the engine behind
26
+ both [tempestroid](https://github.com/mauriciobenjamin700/tempestroid) (native
27
+ renderers: Qt / Compose / Android) and tempestweb (DOM, WASM + server modes).
28
+
29
+ It is the *one tree, many renderers* core: the IR, the reconciler (`build` /
30
+ `diff`), the state model (`App`), the typed style model (`Style`, `Color`, `Edge`),
31
+ the widget and component trees, plus the cross-cutting helpers (animation, i18n,
32
+ navigation, theme, validators). It carries **no platform-coupled code** — no Qt, no
33
+ JNI, no Android, no DOM — so it imports cleanly under CPython, Pyodide and a
34
+ headless server. Renderers live in the consumers; this package only produces and
35
+ diffs the tree.
36
+
37
+ ```python
38
+ from tempest_core import App, Column, Text, Button, Style, build, diff
39
+
40
+ old = build(Column(children=[Text(content="Count: 0", key="label")]))
41
+ new = build(Column(children=[Text(content="Count: 1", key="label")]))
42
+ patches = diff(old, new) # -> [Update(set_props={"content": "Count: 1"})]
43
+ ```
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ pip install tempest-core
49
+ ```
50
+
51
+ Requires Python `>=3.11`. Only hard dependency: `pydantic>=2`.
52
+
53
+ ## Status
54
+
55
+ Extracted from tempestroid's vendored core. **tempestweb** consumes it directly
56
+ (no vendored copy). tempestroid's own migration to depend on this package — with
57
+ its full Qt↔Compose conformance suite green — is the next phase.
58
+
59
+ ## Develop
60
+
61
+ ```bash
62
+ uv sync --extra dev
63
+ ruff check . && ruff format --check .
64
+ mypy tempest_core
65
+ pytest -q
66
+ ```
@@ -0,0 +1,47 @@
1
+ # tempest-core
2
+
3
+ 📚 **Documentation:** [Português (Brasil)](https://mauriciobenjamin700.github.io/tempest-core/)
4
+ · [English (US)](https://mauriciobenjamin700.github.io/tempest-core/en/) — bilingual, on GitHub Pages.
5
+
6
+ **Renderer-agnostic UI core** shared across the tempest stack — the engine behind
7
+ both [tempestroid](https://github.com/mauriciobenjamin700/tempestroid) (native
8
+ renderers: Qt / Compose / Android) and tempestweb (DOM, WASM + server modes).
9
+
10
+ It is the *one tree, many renderers* core: the IR, the reconciler (`build` /
11
+ `diff`), the state model (`App`), the typed style model (`Style`, `Color`, `Edge`),
12
+ the widget and component trees, plus the cross-cutting helpers (animation, i18n,
13
+ navigation, theme, validators). It carries **no platform-coupled code** — no Qt, no
14
+ JNI, no Android, no DOM — so it imports cleanly under CPython, Pyodide and a
15
+ headless server. Renderers live in the consumers; this package only produces and
16
+ diffs the tree.
17
+
18
+ ```python
19
+ from tempest_core import App, Column, Text, Button, Style, build, diff
20
+
21
+ old = build(Column(children=[Text(content="Count: 0", key="label")]))
22
+ new = build(Column(children=[Text(content="Count: 1", key="label")]))
23
+ patches = diff(old, new) # -> [Update(set_props={"content": "Count: 1"})]
24
+ ```
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ pip install tempest-core
30
+ ```
31
+
32
+ Requires Python `>=3.11`. Only hard dependency: `pydantic>=2`.
33
+
34
+ ## Status
35
+
36
+ Extracted from tempestroid's vendored core. **tempestweb** consumes it directly
37
+ (no vendored copy). tempestroid's own migration to depend on this package — with
38
+ its full Qt↔Compose conformance suite green — is the next phase.
39
+
40
+ ## Develop
41
+
42
+ ```bash
43
+ uv sync --extra dev
44
+ ruff check . && ruff format --check .
45
+ mypy tempest_core
46
+ pytest -q
47
+ ```
@@ -0,0 +1,38 @@
1
+ # tempest-core
2
+
3
+ **The renderer-agnostic UI core** — the engine behind
4
+ [tempestroid](https://github.com/mauriciobenjamin700/tempestroid) (native
5
+ renderers: Qt / Compose / Android) and tempestweb (DOM, WASM + server modes). 🌩️
6
+
7
+ One tree, many renderers. You describe the UI as **typed Python** and the core
8
+ produces an immutable tree (the IR) and computes the **diff** between two trees —
9
+ renderers just apply the patches.
10
+
11
+ ```python
12
+ from tempest_core import Column, Text, build, diff
13
+
14
+ old = build(Column(children=[Text(content="Count: 0", key="label")]))
15
+ new = build(Column(children=[Text(content="Count: 1", key="label")]))
16
+
17
+ diff(old, new)
18
+ # -> [Update(set_props={"content": "Count: 1"})]
19
+ ```
20
+
21
+ ## What's inside
22
+
23
+ | Layer | What it does |
24
+ |---|---|
25
+ | **IR + reconciler** | `build` / `diff` (and `build_scene` / `diff_scene` for overlays), the `Node` / `Patch` model, and `App` with a coalesced rebuild loop |
26
+ | **Typed style** | `Style`, `Color`, `Edge`, gradients, shadows, borders, transitions — no CSS cascade, inline and typed |
27
+ | **Widgets & components** | layout (Column/Row/Container/Stack), text, button, inputs, checkbox, lists (LazyColumn/Row/Grid), overlays, gestures, media, + composed components (cards, forms, fields, tables, BR inputs…) |
28
+ | **Cross-cutting** | animation, i18n, navigation (`Route`/`NavStack`), theme, validators (CPF/CNPJ/email/phone), icons |
29
+
30
+ !!! tip "No platform-coupled code"
31
+ No Qt, JNI, Android or DOM here — the core imports cleanly under CPython,
32
+ Pyodide and a headless server. Only hard dependency: `pydantic>=2`.
33
+
34
+ ## Next steps
35
+
36
+ - [Installation](installation.md) — `pip install tempest-core`.
37
+ - [Tutorial](tutorial/index.md) — build and diff your first tree.
38
+ - [API reference](reference.md) — the public symbols.
@@ -0,0 +1,38 @@
1
+ # tempest-core
2
+
3
+ **O core de UI agnóstico de renderizador** — o motor por trás do
4
+ [tempestroid](https://github.com/mauriciobenjamin700/tempestroid) (renderizadores
5
+ nativos: Qt / Compose / Android) e do tempestweb (DOM, modos WASM + servidor). 🌩️
6
+
7
+ Uma árvore, muitos renderizadores. Você descreve a UI como **Python tipado** e o
8
+ core produz uma árvore imutável (a IR) e calcula o **diff** entre duas árvores —
9
+ os renderizadores só aplicam os patches.
10
+
11
+ ```python
12
+ from tempest_core import Column, Text, build, diff
13
+
14
+ antiga = build(Column(children=[Text(content="Contagem: 0", key="label")]))
15
+ nova = build(Column(children=[Text(content="Contagem: 1", key="label")]))
16
+
17
+ diff(antiga, nova)
18
+ # -> [Update(set_props={"content": "Contagem: 1"})]
19
+ ```
20
+
21
+ ## O que tem dentro
22
+
23
+ | Camada | O que faz |
24
+ |---|---|
25
+ | **IR + reconciliador** | `build` / `diff` (e `build_scene` / `diff_scene` para overlays), o modelo `Node` / `Patch`, e o `App` com loop de rebuild coalescido |
26
+ | **Estilo tipado** | `Style`, `Color`, `Edge`, gradientes, sombras, bordas, transições — sem cascata CSS, inline e tipado |
27
+ | **Widgets & componentes** | layout (Column/Row/Container/Stack), texto, botão, inputs, checkbox, listas (LazyColumn/Row/Grid), overlays, gestos, mídia, + componentes compostos (cards, forms, campos, tabelas, inputs BR…) |
28
+ | **Transversais** | animação, i18n, navegação (`Route`/`NavStack`), tema, validadores (CPF/CNPJ/email/telefone), ícones |
29
+
30
+ !!! tip "Sem código acoplado a plataforma"
31
+ Nada de Qt, JNI, Android ou DOM aqui — o core importa limpo sob CPython,
32
+ Pyodide e um servidor headless. Única dependência dura: `pydantic>=2`.
33
+
34
+ ## Próximos passos
35
+
36
+ - [Instalação](installation.md) — `pip install tempest-core`.
37
+ - [Tutorial](tutorial/index.md) — construa e diffe sua primeira árvore.
38
+ - [Referência da API](reference.md) — os símbolos públicos.
@@ -0,0 +1,32 @@
1
+ # Installation
2
+
3
+ ```bash
4
+ pip install tempest-core
5
+ ```
6
+
7
+ Requires Python `>=3.11`. The only hard dependency is `pydantic>=2`.
8
+
9
+ !!! note "Who uses tempest-core"
10
+ You rarely install `tempest-core` on its own — it comes as a dependency of
11
+ **tempestweb** (web) or **tempestroid** (native). But the core is standalone:
12
+ you can build and diff trees with no renderer at all.
13
+
14
+ ## Verify
15
+
16
+ ```bash
17
+ python -c "from tempest_core import App, Column, Text, build, diff; print('OK')"
18
+ ```
19
+
20
+ ## Develop
21
+
22
+ ```bash
23
+ uv sync --extra dev
24
+ ruff check . && ruff format --check .
25
+ pyright tempest_core
26
+ pytest -q
27
+ ```
28
+
29
+ ## Recap
30
+
31
+ - `pip install tempest-core` — Python 3.11+, only needs pydantic.
32
+ - Import everything from the top level: `from tempest_core import …`.
@@ -0,0 +1,32 @@
1
+ # Instalação
2
+
3
+ ```bash
4
+ pip install tempest-core
5
+ ```
6
+
7
+ Requer Python `>=3.11`. A única dependência dura é o `pydantic>=2`.
8
+
9
+ !!! note "Quem usa o tempest-core"
10
+ Você raramente instala o `tempest-core` sozinho — ele vem como dependência do
11
+ **tempestweb** (web) ou do **tempestroid** (nativo). Mas o core é autônomo:
12
+ dá pra construir e diffar árvores sem nenhum renderizador.
13
+
14
+ ## Verificar
15
+
16
+ ```bash
17
+ python -c "from tempest_core import App, Column, Text, build, diff; print('OK')"
18
+ ```
19
+
20
+ ## Desenvolvimento
21
+
22
+ ```bash
23
+ uv sync --extra dev
24
+ ruff check . && ruff format --check .
25
+ pyright tempest_core
26
+ pytest -q
27
+ ```
28
+
29
+ ## Recapitulando
30
+
31
+ - `pip install tempest-core` — Python 3.11+, só precisa de pydantic.
32
+ - Importe tudo do nível de topo: `from tempest_core import …`.
@@ -0,0 +1,9 @@
1
+ # API reference
2
+
3
+ The full, auto-generated API (docstrings are in English) lives on the shared
4
+ reference page:
5
+
6
+ [Open the API reference →](../reference/){ .md-button }
7
+
8
+ It documents every public symbol of `tempest_core` — `App`, `build`, `diff`,
9
+ `Style`, the widgets and the cross-cutting helpers.
@@ -0,0 +1,8 @@
1
+ # Referência da API
2
+
3
+ Os símbolos públicos do `tempest_core`, gerados a partir das docstrings.
4
+
5
+ ::: tempest_core
6
+ options:
7
+ show_root_heading: false
8
+ heading_level: 2
@@ -0,0 +1,54 @@
1
+ # Tutorial — build & diff
2
+
3
+ The heart of tempest-core is two functions: **`build`** turns a widget into an
4
+ immutable tree (the IR), and **`diff`** compares two trees and returns the minimal
5
+ list of **patches**. A renderer applies those patches; the core never touches
6
+ pixels.
7
+
8
+ ## Build a tree
9
+
10
+ ```python
11
+ from tempest_core import Column, Text, build
12
+
13
+ tree = build(
14
+ Column(
15
+ children=[
16
+ Text(content="Hello", key="greeting"), # (1)!
17
+ ],
18
+ )
19
+ )
20
+ print(tree.type) # "Column"
21
+ print(tree.children[0].props["content"]) # "Hello"
22
+ ```
23
+
24
+ 1. `key` is the node's stable identity — it's how the diff matches nodes across
25
+ rebuilds. Give a `key` to anything that can change position or content.
26
+
27
+ `build` returns an immutable `Node`: `{type, key, props, children}`.
28
+
29
+ ## Diff two trees
30
+
31
+ ```python
32
+ from tempest_core import Column, Text, build, diff
33
+
34
+ a = build(Column(children=[Text(content="Hello", key="g")]))
35
+ b = build(Column(children=[Text(content="Bye", key="g")]))
36
+
37
+ patches = diff(a, b)
38
+ print(patches[0].model_dump(mode="json"))
39
+ # {"path": [0], "set_props": {"content": "Bye"}, "unset_props": []}
40
+ ```
41
+
42
+ The diff is **keyed and minimal**: only what changed becomes a patch. The five
43
+ kinds are `Update`, `Insert`, `Remove`, `Reorder` and `Replace`.
44
+
45
+ !!! info "Why this matters"
46
+ The renderer gets only the delta — it never rebuilds the whole screen. Same
47
+ model as React, but the tree is typed Python, no JSX.
48
+
49
+ ## Recap
50
+
51
+ - `build(widget) -> Node` — the immutable tree.
52
+ - `diff(old, new) -> list[Patch]` — the minimal, keyed delta.
53
+ - Give nodes a `key` so the diff matches correctly.
54
+ - Next: [state and rebuilds](state.md).
@@ -0,0 +1,53 @@
1
+ # Tutorial — build & diff
2
+
3
+ O coração do tempest-core são duas funções: **`build`** transforma um widget numa
4
+ árvore imutável (a IR), e **`diff`** compara duas árvores e devolve a lista mínima
5
+ de **patches**. Um renderizador aplica esses patches; o core nunca toca em pixels.
6
+
7
+ ## Construir uma árvore
8
+
9
+ ```python
10
+ from tempest_core import Column, Text, build
11
+
12
+ árvore = build(
13
+ Column(
14
+ children=[
15
+ Text(content="Olá", key="saudacao"), # (1)!
16
+ ],
17
+ )
18
+ )
19
+ print(árvore.type) # "Column"
20
+ print(árvore.children[0].props["content"]) # "Olá"
21
+ ```
22
+
23
+ 1. `key` é a identidade estável do nó — é por ela que o diff casa nós entre
24
+ rebuilds. Dê `key` a tudo que pode mudar de posição ou conteúdo.
25
+
26
+ `build` devolve um `Node` imutável: `{type, key, props, children}`.
27
+
28
+ ## Diffar duas árvores
29
+
30
+ ```python
31
+ from tempest_core import Column, Text, build, diff
32
+
33
+ a = build(Column(children=[Text(content="Olá", key="s")]))
34
+ b = build(Column(children=[Text(content="Tchau", key="s")]))
35
+
36
+ patches = diff(a, b)
37
+ print(patches[0].model_dump(mode="json"))
38
+ # {"path": [0], "set_props": {"content": "Tchau"}, "unset_props": []}
39
+ ```
40
+
41
+ O diff é **keyed e mínimo**: só o que mudou vira patch. Os cinco tipos são
42
+ `Update`, `Insert`, `Remove`, `Reorder` e `Replace`.
43
+
44
+ !!! info "Por que isso importa"
45
+ O renderizador recebe só o delta — não reconstrói a tela inteira. É o mesmo
46
+ modelo do React, mas a árvore é Python tipado, sem JSX.
47
+
48
+ ## Recapitulando
49
+
50
+ - `build(widget) -> Node` — a árvore imutável.
51
+ - `diff(antiga, nova) -> list[Patch]` — o delta mínimo, keyed.
52
+ - Dê `key` aos nós para o diff casar corretamente.
53
+ - Próximo: [estado e rebuilds](state.md).
@@ -0,0 +1,52 @@
1
+ # 2. State and rebuilds
2
+
3
+ A live UI = state + a function from state to tree. The **`App`** holds the state,
4
+ runs your `view(app)`, and on a state change rebuilds and diffs — emitting patches
5
+ through an `apply_patches` callback.
6
+
7
+ ```python
8
+ from dataclasses import dataclass
9
+
10
+ from tempest_core import App, Column, Text, Widget
11
+
12
+
13
+ @dataclass
14
+ class State:
15
+ value: int = 0
16
+
17
+
18
+ emitted = []
19
+
20
+
21
+ def view(app: App[State]) -> Widget:
22
+ return Column(children=[Text(content=f"Count: {app.state.value}", key="lbl")])
23
+
24
+
25
+ app = App(state=State(), view=view, apply_patches=emitted.append) # (1)!
26
+ app.start() # (2)!
27
+ app.set_state(lambda s: setattr(s, "value", 1)) # (3)!
28
+ ```
29
+
30
+ 1. `apply_patches` receives each tick's patch list. A renderer applies it; here we
31
+ just collect.
32
+ 2. `start()` builds the initial scene.
33
+ 3. `set_state` mutates state and **schedules a coalesced rebuild** — several
34
+ changes in one tick become a single diff.
35
+
36
+ !!! note "The view is pure"
37
+ `view()` only **reads** `app.state` and describes the UI. Changing state is the
38
+ handlers' job, via `set_state`. The view never mutates anything.
39
+
40
+ ## Navigation is state too
41
+
42
+ The `App` owns a `NavStack` (`app.nav`): `push` / `pop` / `replace` / `reset`
43
+ change the top route and schedule a rebuild — the `view` reads `app.nav.top` and
44
+ draws the screen. No new IR node: changing routes is just the view producing a
45
+ different tree.
46
+
47
+ ## Recap
48
+
49
+ - `App(state, view, apply_patches)` + `start()` starts the UI.
50
+ - `set_state(mutator)` schedules a coalesced rebuild → diff → patches.
51
+ - `app.nav` (`push`/`pop`/`reset`) is navigation as state.
52
+ - Next: [styling](style.md).
@@ -0,0 +1,51 @@
1
+ # 2. Estado e rebuilds
2
+
3
+ Uma UI viva = estado + uma função do estado para a árvore. O **`App`** segura o
4
+ estado, roda sua `view(app)` e, quando o estado muda, reconstrói e diffa — emitindo
5
+ patches por um callback `apply_patches`.
6
+
7
+ ```python
8
+ from dataclasses import dataclass
9
+
10
+ from tempest_core import App, Column, Text, Widget
11
+
12
+
13
+ @dataclass
14
+ class Estado:
15
+ valor: int = 0
16
+
17
+
18
+ patches_emitidos = []
19
+
20
+
21
+ def view(app: App[Estado]) -> Widget:
22
+ return Column(children=[Text(content=f"Contagem: {app.state.valor}", key="lbl")])
23
+
24
+
25
+ app = App(state=Estado(), view=view, apply_patches=patches_emitidos.append) # (1)!
26
+ app.start() # (2)!
27
+ app.set_state(lambda s: setattr(s, "valor", 1)) # (3)!
28
+ ```
29
+
30
+ 1. `apply_patches` recebe a lista de patches de cada tick. Um renderizador a
31
+ aplica; aqui só guardamos.
32
+ 2. `start()` constrói a cena inicial.
33
+ 3. `set_state` muta o estado e **agenda um rebuild coalescido** — várias mudanças
34
+ no mesmo tick viram um único diff.
35
+
36
+ !!! note "A view é pura"
37
+ `view()` só **lê** `app.state` e descreve a UI. Mudar estado é trabalho dos
38
+ handlers, via `set_state`. A view nunca muta nada.
39
+
40
+ ## Navegação também é estado
41
+
42
+ O `App` tem uma `NavStack` (`app.nav`): `push` / `pop` / `replace` / `reset`
43
+ mudam a rota no topo e agendam rebuild — a `view` lê `app.nav.top` e desenha a
44
+ tela. Sem nó de IR novo: trocar de rota é a view produzindo uma árvore diferente.
45
+
46
+ ## Recapitulando
47
+
48
+ - `App(state, view, apply_patches)` + `start()` inicia a UI.
49
+ - `set_state(mutator)` agenda um rebuild coalescido → diff → patches.
50
+ - `app.nav` (`push`/`pop`/`reset`) é navegação como estado.
51
+ - Próximo: [estilo](style.md).