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.
- tempest_core-0.1.0/.github/workflows/ci.yml +26 -0
- tempest_core-0.1.0/.github/workflows/docs.yml +59 -0
- tempest_core-0.1.0/.github/workflows/publish.yml +43 -0
- tempest_core-0.1.0/.gitignore +8 -0
- tempest_core-0.1.0/CHANGELOG.md +34 -0
- tempest_core-0.1.0/PKG-INFO +66 -0
- tempest_core-0.1.0/README.md +47 -0
- tempest_core-0.1.0/docs/index.en.md +38 -0
- tempest_core-0.1.0/docs/index.md +38 -0
- tempest_core-0.1.0/docs/installation.en.md +32 -0
- tempest_core-0.1.0/docs/installation.md +32 -0
- tempest_core-0.1.0/docs/reference.en.md +9 -0
- tempest_core-0.1.0/docs/reference.md +8 -0
- tempest_core-0.1.0/docs/tutorial/index.en.md +54 -0
- tempest_core-0.1.0/docs/tutorial/index.md +53 -0
- tempest_core-0.1.0/docs/tutorial/state.en.md +52 -0
- tempest_core-0.1.0/docs/tutorial/state.md +51 -0
- tempest_core-0.1.0/docs/tutorial/style.en.md +49 -0
- tempest_core-0.1.0/docs/tutorial/style.md +49 -0
- tempest_core-0.1.0/mkdocs.yml +114 -0
- tempest_core-0.1.0/pyproject.toml +61 -0
- tempest_core-0.1.0/tempest_core/__init__.py +86 -0
- tempest_core-0.1.0/tempest_core/animation.py +425 -0
- tempest_core-0.1.0/tempest_core/components/__init__.py +119 -0
- tempest_core-0.1.0/tempest_core/components/bars.py +266 -0
- tempest_core-0.1.0/tempest_core/components/base.py +54 -0
- tempest_core-0.1.0/tempest_core/components/brforms.py +461 -0
- tempest_core-0.1.0/tempest_core/components/cards.py +200 -0
- tempest_core-0.1.0/tempest_core/components/dates.py +221 -0
- tempest_core-0.1.0/tempest_core/components/disclosure.py +83 -0
- tempest_core-0.1.0/tempest_core/components/feedback.py +194 -0
- tempest_core-0.1.0/tempest_core/components/fields.py +204 -0
- tempest_core-0.1.0/tempest_core/components/layout.py +179 -0
- tempest_core-0.1.0/tempest_core/components/mediainputs.py +254 -0
- tempest_core-0.1.0/tempest_core/components/menu.py +111 -0
- tempest_core-0.1.0/tempest_core/components/navigation.py +215 -0
- tempest_core-0.1.0/tempest_core/components/selection.py +269 -0
- tempest_core-0.1.0/tempest_core/components/table.py +235 -0
- tempest_core-0.1.0/tempest_core/core/__init__.py +45 -0
- tempest_core-0.1.0/tempest_core/core/introspection.py +271 -0
- tempest_core-0.1.0/tempest_core/core/ir.py +159 -0
- tempest_core-0.1.0/tempest_core/core/reconciler.py +302 -0
- tempest_core-0.1.0/tempest_core/core/state.py +656 -0
- tempest_core-0.1.0/tempest_core/devices.py +144 -0
- tempest_core-0.1.0/tempest_core/i18n.py +90 -0
- tempest_core-0.1.0/tempest_core/icons.py +394 -0
- tempest_core-0.1.0/tempest_core/navigation.py +114 -0
- tempest_core-0.1.0/tempest_core/py.typed +0 -0
- tempest_core-0.1.0/tempest_core/style.py +722 -0
- tempest_core-0.1.0/tempest_core/theme.py +126 -0
- tempest_core-0.1.0/tempest_core/validators.py +168 -0
- tempest_core-0.1.0/tempest_core/widgets/__init__.py +352 -0
- tempest_core-0.1.0/tempest_core/widgets/animated.py +283 -0
- tempest_core-0.1.0/tempest_core/widgets/base.py +386 -0
- tempest_core-0.1.0/tempest_core/widgets/button.py +31 -0
- tempest_core-0.1.0/tempest_core/widgets/events.py +760 -0
- tempest_core-0.1.0/tempest_core/widgets/forms.py +241 -0
- tempest_core-0.1.0/tempest_core/widgets/gestures.py +426 -0
- tempest_core-0.1.0/tempest_core/widgets/indicators.py +53 -0
- tempest_core-0.1.0/tempest_core/widgets/inputs.py +553 -0
- tempest_core-0.1.0/tempest_core/widgets/layout.py +370 -0
- tempest_core-0.1.0/tempest_core/widgets/lists.py +518 -0
- tempest_core-0.1.0/tempest_core/widgets/media.py +613 -0
- tempest_core-0.1.0/tempest_core/widgets/navigation_widgets.py +182 -0
- tempest_core-0.1.0/tempest_core/widgets/overlays.py +270 -0
- tempest_core-0.1.0/tempest_core/widgets/text.py +19 -0
- 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,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,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).
|