carterkit 0.3.1__tar.gz → 0.5.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.
- {carterkit-0.3.1 → carterkit-0.5.0}/PKG-INFO +63 -18
- carterkit-0.5.0/README.md +141 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/__init__.py +14 -5
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/buffer.py +8 -2
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/control-def.md +5 -3
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/gauge.md +10 -3
- carterkit-0.5.0/carterkit/controldocs/grid-dimensions.md +93 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/index.md +9 -4
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/map.md +1 -1
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/privacy.md +1 -1
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/progress-ring.md +9 -1
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/terms.md +1 -1
- carterkit-0.5.0/carterkit/declare.py +280 -0
- carterkit-0.5.0/carterkit/dynamic.py +99 -0
- carterkit-0.5.0/carterkit/layout.py +458 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit.egg-info/PKG-INFO +63 -18
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit.egg-info/SOURCES.txt +6 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/pyproject.toml +1 -1
- carterkit-0.5.0/tests/test_client.py +153 -0
- carterkit-0.5.0/tests/test_declare.py +109 -0
- carterkit-0.5.0/tests/test_dynamic.py +67 -0
- carterkit-0.5.0/tests/test_ui.py +92 -0
- carterkit-0.3.1/README.md +0 -96
- carterkit-0.3.1/carterkit/layout.py +0 -87
- carterkit-0.3.1/tests/test_client.py +0 -32
- {carterkit-0.3.1 → carterkit-0.5.0}/LICENSE +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/__main__.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/bind.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/catalog.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/cli.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/client.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/codegen.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/accordion.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/actions.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/animations.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/appearance.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/button.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/cardList.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/carousel.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/chat.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/color-picker.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/date-picker.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/divider.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/flip-card.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/graph.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/group-def.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/haptics.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/image.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/joystick.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/label.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/layout-config.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/list.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/log-console.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/long-press.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/picker.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/pulse.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/qr-code.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/segmented.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/slider.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/spacer.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/sparkline.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/status-light.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/stepper.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/sync.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/text-input.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/theming.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/toggle.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/visibility.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/web-view.md +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controls.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/e2ee.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/grid.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/infer.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/py.typed +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/theming.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/tune.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/validate.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit.egg-info/dependency_links.txt +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit.egg-info/entry_points.txt +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit.egg-info/requires.txt +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/carterkit.egg-info/top_level.txt +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/setup.cfg +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_bind.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_buffer.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_catalog.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_cli.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_codegen.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_controls.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_e2ee.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_grid.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_infer.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_layout.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_theming.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_tune.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_validate.py +0 -0
- {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_validate_bindings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: carterkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Build and drive CAR-TER layouts from Python — the control docs are the library.
|
|
5
5
|
Author: Carter Beaudoin
|
|
6
6
|
License-Expression: MIT
|
|
@@ -51,25 +51,70 @@ carterkit.examples("button") # documented example snippets
|
|
|
51
51
|
|
|
52
52
|
## Build a layout
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
layout:
|
|
54
|
+
Controls are **methods on the layout**, ids are positional, tabs and groups are context
|
|
55
|
+
managers, and bindings fold into kwargs. Each control method returns a **handle** you can
|
|
56
|
+
use as a binding target or patch later. Unknown control types and bad enum values raise
|
|
57
|
+
instead of silently shipping a broken layout:
|
|
58
58
|
|
|
59
59
|
```python
|
|
60
|
-
from carterkit import Layout
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
print(
|
|
71
|
-
|
|
72
|
-
|
|
60
|
+
from carterkit import Layout
|
|
61
|
+
|
|
62
|
+
with Layout("Dashboard", cols=4, rows=4) as ui:
|
|
63
|
+
ui.connect("ws://192.168.1.50:8765", channel="home")
|
|
64
|
+
with ui.tab("Main", icon="gauge"):
|
|
65
|
+
cpu = ui.gauge("cpu", label="CPU", min=0, max=100, span=(2, 2),
|
|
66
|
+
listen="cpu", when={"msg_type": "metrics"})
|
|
67
|
+
ui.status_light("warn", visible=cpu > 90) # handle → visibility condition
|
|
68
|
+
ui.button("refresh", label="Refresh", send="refresh", request=True)
|
|
69
|
+
|
|
70
|
+
print(ui.findings()) # schema + grid + binding lint against the bundled catalog
|
|
71
|
+
ui.save("dashboard.json") # the composed layout, ready to push/load
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Binding sugar: `listen=`/`when=`/`event=` build a `sync`, and `send=`/`request=`/`payload=`
|
|
75
|
+
build an `action`; pass `sync=[...]`/`action={...}` (via `carterkit.bind`) for anything
|
|
76
|
+
fancier. A handle comparison (`cpu > 90`) becomes a real visibility condition; `==`/`!=`
|
|
77
|
+
stay normal Python, so use `.eq()`/`.neq()`. `help(carterkit.build.gauge)` prints any
|
|
78
|
+
control's documentation, straight from the bundled docs.
|
|
79
|
+
|
|
80
|
+
**Prefer a declarative style?** A class veneer compiles to the *same* layout — ids come
|
|
81
|
+
from attribute names, tabs/groups are nested classes (great for fixed dashboards; the flat
|
|
82
|
+
builder reads better for generated ones):
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from carterkit.declare import Screen, Tab, Connect, Gauge, Button, StatusLight
|
|
86
|
+
|
|
87
|
+
class Dashboard(Screen, cols=4, rows=4):
|
|
88
|
+
relay = Connect("ws://192.168.1.50:8765", channel="home")
|
|
89
|
+
class Main(Tab, icon="gauge"):
|
|
90
|
+
cpu = Gauge(label="CPU", min=0, max=100, span=(2, 2), listen="cpu")
|
|
91
|
+
warn = StatusLight(visible=cpu > 90)
|
|
92
|
+
refresh = Button(label="Refresh", send="refresh", request=True)
|
|
93
|
+
|
|
94
|
+
Dashboard.save("dashboard.json")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Dynamic groups
|
|
98
|
+
|
|
99
|
+
Generate controls in `for`/`if` loops (auto-placed in the group's own grid), or mark a
|
|
100
|
+
group `dynamic="event"` and replace its children live at runtime. Build that replacement
|
|
101
|
+
payload with `Fragment`, then lint it against the broadcasts your server actually emits —
|
|
102
|
+
catching events that never arrive, missing `children` arrays, and off-grid/invalid
|
|
103
|
+
injected controls before they ship:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
import carterkit
|
|
107
|
+
from carterkit import Fragment
|
|
108
|
+
|
|
109
|
+
ui.group("Now Playing", span=(3, 4), cols=4, rows=3, dynamic="player_state")
|
|
110
|
+
|
|
111
|
+
frag = Fragment(cols=4, rows=3)
|
|
112
|
+
frag.label("title", text="Song", span=(1, 4))
|
|
113
|
+
frag.button("play", label="Play", send="play")
|
|
114
|
+
# your server broadcasts frag.payload("player_state") == {"msg_type": ..., "children": [...]}
|
|
115
|
+
|
|
116
|
+
print(carterkit.format_findings(
|
|
117
|
+
carterkit.lint_dynamic_traffic(ui.layout, [frag.payload("player_state")])))
|
|
73
118
|
```
|
|
74
119
|
|
|
75
120
|
Prefer surgical edits? `LayoutBuffer` gives `add_control` / `update_control` / `move_control`
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# carterkit
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/carterkit/)
|
|
4
|
+
[](https://pepy.tech/project/carterkit)
|
|
5
|
+
[](https://pypi.org/project/carterkit/)
|
|
6
|
+
|
|
7
|
+
Build and drive [CAR-TER](https://carterbeaudoin.net/CAR-TER) layouts from Python.
|
|
8
|
+
|
|
9
|
+
**The control docs are the library.** Every control's schema, fields, and examples
|
|
10
|
+
are parsed at runtime from the ControlDocs markdown bundled inside the package — the
|
|
11
|
+
exact same docs the CAR-TER app renders — so the catalog never drifts from the
|
|
12
|
+
definitions.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install carterkit
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Explore the controls (zero config)
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import carterkit
|
|
22
|
+
|
|
23
|
+
carterkit.controls() # {type: schema} for every placeable control
|
|
24
|
+
carterkit.doc("gauge") # full parsed doc: fields, themeFields, examples
|
|
25
|
+
print(carterkit.doc_markdown("gauge")) # the rendered documentation prose
|
|
26
|
+
carterkit.examples("button") # documented example snippets
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Build a layout
|
|
30
|
+
|
|
31
|
+
Controls are **methods on the layout**, ids are positional, tabs and groups are context
|
|
32
|
+
managers, and bindings fold into kwargs. Each control method returns a **handle** you can
|
|
33
|
+
use as a binding target or patch later. Unknown control types and bad enum values raise
|
|
34
|
+
instead of silently shipping a broken layout:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from carterkit import Layout
|
|
38
|
+
|
|
39
|
+
with Layout("Dashboard", cols=4, rows=4) as ui:
|
|
40
|
+
ui.connect("ws://192.168.1.50:8765", channel="home")
|
|
41
|
+
with ui.tab("Main", icon="gauge"):
|
|
42
|
+
cpu = ui.gauge("cpu", label="CPU", min=0, max=100, span=(2, 2),
|
|
43
|
+
listen="cpu", when={"msg_type": "metrics"})
|
|
44
|
+
ui.status_light("warn", visible=cpu > 90) # handle → visibility condition
|
|
45
|
+
ui.button("refresh", label="Refresh", send="refresh", request=True)
|
|
46
|
+
|
|
47
|
+
print(ui.findings()) # schema + grid + binding lint against the bundled catalog
|
|
48
|
+
ui.save("dashboard.json") # the composed layout, ready to push/load
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Binding sugar: `listen=`/`when=`/`event=` build a `sync`, and `send=`/`request=`/`payload=`
|
|
52
|
+
build an `action`; pass `sync=[...]`/`action={...}` (via `carterkit.bind`) for anything
|
|
53
|
+
fancier. A handle comparison (`cpu > 90`) becomes a real visibility condition; `==`/`!=`
|
|
54
|
+
stay normal Python, so use `.eq()`/`.neq()`. `help(carterkit.build.gauge)` prints any
|
|
55
|
+
control's documentation, straight from the bundled docs.
|
|
56
|
+
|
|
57
|
+
**Prefer a declarative style?** A class veneer compiles to the *same* layout — ids come
|
|
58
|
+
from attribute names, tabs/groups are nested classes (great for fixed dashboards; the flat
|
|
59
|
+
builder reads better for generated ones):
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from carterkit.declare import Screen, Tab, Connect, Gauge, Button, StatusLight
|
|
63
|
+
|
|
64
|
+
class Dashboard(Screen, cols=4, rows=4):
|
|
65
|
+
relay = Connect("ws://192.168.1.50:8765", channel="home")
|
|
66
|
+
class Main(Tab, icon="gauge"):
|
|
67
|
+
cpu = Gauge(label="CPU", min=0, max=100, span=(2, 2), listen="cpu")
|
|
68
|
+
warn = StatusLight(visible=cpu > 90)
|
|
69
|
+
refresh = Button(label="Refresh", send="refresh", request=True)
|
|
70
|
+
|
|
71
|
+
Dashboard.save("dashboard.json")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Dynamic groups
|
|
75
|
+
|
|
76
|
+
Generate controls in `for`/`if` loops (auto-placed in the group's own grid), or mark a
|
|
77
|
+
group `dynamic="event"` and replace its children live at runtime. Build that replacement
|
|
78
|
+
payload with `Fragment`, then lint it against the broadcasts your server actually emits —
|
|
79
|
+
catching events that never arrive, missing `children` arrays, and off-grid/invalid
|
|
80
|
+
injected controls before they ship:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
import carterkit
|
|
84
|
+
from carterkit import Fragment
|
|
85
|
+
|
|
86
|
+
ui.group("Now Playing", span=(3, 4), cols=4, rows=3, dynamic="player_state")
|
|
87
|
+
|
|
88
|
+
frag = Fragment(cols=4, rows=3)
|
|
89
|
+
frag.label("title", text="Song", span=(1, 4))
|
|
90
|
+
frag.button("play", label="Play", send="play")
|
|
91
|
+
# your server broadcasts frag.payload("player_state") == {"msg_type": ..., "children": [...]}
|
|
92
|
+
|
|
93
|
+
print(carterkit.format_findings(
|
|
94
|
+
carterkit.lint_dynamic_traffic(ui.layout, [frag.payload("player_state")])))
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Prefer surgical edits? `LayoutBuffer` gives `add_control` / `update_control` / `move_control`
|
|
98
|
+
over a held draft; `lay.buffer` exposes it.
|
|
99
|
+
|
|
100
|
+
`infer.build_layout(payload)` generates a wired layout from a sample telemetry dict;
|
|
101
|
+
`codegen.generate_service_stub(layout)` emits a runnable MeshSocket server skeleton;
|
|
102
|
+
`theming.theme_for(...)` and `tune.tune_gauge(...)` round out the authoring tools.
|
|
103
|
+
|
|
104
|
+
## CLI
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
carterkit catalog # list every control type
|
|
108
|
+
carterkit doc gauge # print a control's documentation
|
|
109
|
+
carterkit examples button # list a control's examples (--name to print one)
|
|
110
|
+
carterkit validate layout.json # lint a layout (exit 1 on errors)
|
|
111
|
+
carterkit gen layout.json # generate a MeshSocket service stub
|
|
112
|
+
carterkit relay --port 8765 # run the bundled MeshSocket relay
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Drive a device
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
import asyncio
|
|
119
|
+
from carterkit import CarterClient
|
|
120
|
+
|
|
121
|
+
async def main():
|
|
122
|
+
async with CarterClient(gateway_url="ws://localhost:18080", token="<mesh token>",
|
|
123
|
+
channel="home", role="device", name="my-hub") as c:
|
|
124
|
+
c.on("toggle", lambda d: {"ok": True, **d})
|
|
125
|
+
await c.broadcast("reading", {"temp_c": 21.4})
|
|
126
|
+
await asyncio.sleep(60)
|
|
127
|
+
# leaving the `async with` disconnects automatically
|
|
128
|
+
|
|
129
|
+
asyncio.run(main())
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
End-to-end encryption (ChaCha20-Poly1305 + per-session salt) is transparent when you
|
|
133
|
+
pass an `e2ee_key`. Send a push to every device on a Connect+ account with
|
|
134
|
+
`CarterClient.notify(...)` or the stdlib-only `carterkit.notify_http(...)`.
|
|
135
|
+
|
|
136
|
+
## Built on
|
|
137
|
+
|
|
138
|
+
[`meshsocket`](https://pypi.org/project/meshsocket/) — the WebSocket mesh transport.
|
|
139
|
+
|
|
140
|
+
The ControlDocs are vendored from the CAR-TER app repo; refresh them with
|
|
141
|
+
`scripts/sync-controldocs.sh`.
|
|
@@ -7,21 +7,24 @@ renders. So the docs never drift from the definitions; they are one and the same
|
|
|
7
7
|
|
|
8
8
|
Quick map:
|
|
9
9
|
- ``controls()`` / ``doc()`` / ``examples()`` — the docs-as-catalog surface
|
|
10
|
+
- ``Layout`` / ``Fragment`` — the flat builder: controls as methods, live handles,
|
|
11
|
+
context-manager tabs/groups (``declare.Screen`` is the declarative-class veneer)
|
|
10
12
|
- ``LayoutBuffer`` — incrementally build a layout (auto-placement, dedupe)
|
|
11
13
|
- ``validate_layout()`` — schema + grid lint against the bundled catalog
|
|
14
|
+
- ``lint_dynamic_traffic()`` — check ``dynamic=`` groups against observed broadcasts
|
|
12
15
|
- ``infer`` / ``codegen`` / ``theming`` / ``tune`` — generate layouts, servers, themes
|
|
13
16
|
- ``CarterClient`` / ``notify_http`` — connect over MeshSocket, push, send alerts
|
|
14
17
|
"""
|
|
15
18
|
from importlib.resources import files
|
|
16
19
|
from pathlib import Path
|
|
17
20
|
|
|
18
|
-
from . import catalog, grid, codegen, infer, theming, tune
|
|
21
|
+
from . import catalog, grid, codegen, infer, theming, tune, dynamic
|
|
19
22
|
from .buffer import LayoutBuffer, BufferError
|
|
20
23
|
from .validate import validate_layout as _validate_layout, format_findings
|
|
21
24
|
from .client import CarterClient, notify_http, CarterNotifyError
|
|
22
25
|
from . import bind
|
|
23
26
|
from .controls import build, control
|
|
24
|
-
from .layout import Layout
|
|
27
|
+
from .layout import Layout, Fragment, Control, Condition
|
|
25
28
|
|
|
26
29
|
try:
|
|
27
30
|
from importlib.metadata import PackageNotFoundError, version as _pkg_version
|
|
@@ -66,12 +69,18 @@ def validate_layout(layout: dict, catalog_: dict = None) -> list:
|
|
|
66
69
|
return _validate_layout(layout, catalog_ if catalog_ is not None else controls(include_theme=True))
|
|
67
70
|
|
|
68
71
|
|
|
72
|
+
def lint_dynamic_traffic(layout: dict, observed, catalog_: dict = None) -> list:
|
|
73
|
+
"""Lint a layout's `dynamic=` groups against observed broadcast payloads. Returns
|
|
74
|
+
`validate`-style findings (see `dynamic.lint_dynamic_traffic`)."""
|
|
75
|
+
return dynamic.lint_dynamic_traffic(layout, observed, catalog=catalog_)
|
|
76
|
+
|
|
77
|
+
|
|
69
78
|
__all__ = [
|
|
70
79
|
"__version__", "PROTOCOL_VERSION",
|
|
71
80
|
"CarterClient", "notify_http", "CarterNotifyError",
|
|
72
81
|
"LayoutBuffer", "BufferError",
|
|
73
82
|
"controls", "doc", "doc_markdown", "examples", "validate_layout",
|
|
74
|
-
"format_findings", "controldocs_dir",
|
|
75
|
-
"build", "control", "bind", "Layout",
|
|
76
|
-
"catalog", "grid", "codegen", "infer", "theming", "tune",
|
|
83
|
+
"lint_dynamic_traffic", "format_findings", "controldocs_dir",
|
|
84
|
+
"build", "control", "bind", "Layout", "Fragment", "Control", "Condition",
|
|
85
|
+
"catalog", "grid", "codegen", "infer", "theming", "tune", "dynamic",
|
|
77
86
|
]
|
|
@@ -168,10 +168,16 @@ class LayoutBuffer:
|
|
|
168
168
|
return ch
|
|
169
169
|
|
|
170
170
|
def add_tab(self, title: str, icon: str = "square.grid.2x2",
|
|
171
|
-
columns: int = DEFAULT_COLUMNS, rows: int = DEFAULT_ROWS
|
|
171
|
+
columns: int = DEFAULT_COLUMNS, rows: int = DEFAULT_ROWS,
|
|
172
|
+
mode: str = None, row_height: int = None) -> int:
|
|
173
|
+
grid = {"columns": columns, "rows": rows}
|
|
174
|
+
if mode is not None:
|
|
175
|
+
grid["mode"] = mode
|
|
176
|
+
if row_height is not None:
|
|
177
|
+
grid["rowHeight"] = row_height
|
|
172
178
|
self.tabs.append({
|
|
173
179
|
"title": title, "icon": icon,
|
|
174
|
-
"grid":
|
|
180
|
+
"grid": grid, "children": [],
|
|
175
181
|
})
|
|
176
182
|
return len(self.tabs) - 1
|
|
177
183
|
|
|
@@ -19,8 +19,8 @@ Every control — regardless of type — shares the same base fields. A control
|
|
|
19
19
|
|
|
20
20
|
| Field | Type | Default | Description |
|
|
21
21
|
|-------|------|---------|-------------|
|
|
22
|
-
| `span` | [
|
|
23
|
-
| `controlHeight` | number | — |
|
|
22
|
+
| `span` | [rowSpan, colSpan] | `[1, 1]` | Grid cells occupied. `colSpan` sets width; in a 2-D grid `rowSpan` sets height (`rowSpan × rowHeight`). Make a control bigger by spanning more cells. See [[grid-dimensions]]. |
|
|
23
|
+
| `controlHeight` | number | — | **Override** the grid-derived height with an exact point value. Rarely needed: in a 2-D grid the cell (`rowSpan × rowHeight`) is the height, and in a `flow` grid shaped controls auto-size to their aspect. Use it to pin a height the grid wouldn't otherwise give. See [[grid-dimensions]]. |
|
|
24
24
|
| `label` | string | — | Display label for the control |
|
|
25
25
|
| `defaultValue` | bool/number/string | — | Initial value before sync |
|
|
26
26
|
| `action` | [[actions\|ActionDefinition]] | — | Command fired on interaction |
|
|
@@ -40,7 +40,8 @@ Every control — regardless of type — shares the same base fields. A control
|
|
|
40
40
|
| `tint` | string | `"#667eea"` | Hex color for accents |
|
|
41
41
|
| `style` | string | varies | Style variant (per control type) |
|
|
42
42
|
| `hideLabel` | bool | `false` | Suppress the label |
|
|
43
|
-
| `
|
|
43
|
+
| `hideValue` | bool | `false` | Hide the numeric readout (e.g. a ring/gauge's center value) so the control becomes a pure compact visual that scales to fill its cell |
|
|
44
|
+
| `hideBackground` | bool | `false` | Remove the glass card behind the control — it floats on the page and fills its cell. Pairs with `hideValue` for a minimal glyph |
|
|
44
45
|
| `formatValue` | string | — | Number formatter for displayed values (see below) |
|
|
45
46
|
|
|
46
47
|
## Value Types
|
|
@@ -90,6 +91,7 @@ The `theme` object overrides theme values for a single control. It accepts the s
|
|
|
90
91
|
Common override keys: `accentColor`, `foregroundColor`, `secondaryColor`, `surfacePrimary`, `borderColor`, `borderWidth`, `cornerRadius`, `controlPadding`, `fontFamily`, `fontDesign`, `labelFontSize`, `valueFontSize`. Toggle/slider/progress controls also accept `trackColor`, `trackActiveColor`, `thumbColor`, `knobColor`, etc. See each control's **Theme Overrides** table for its specific keys, and [[theming]] for the full system.
|
|
91
92
|
|
|
92
93
|
## Related
|
|
94
|
+
- [[grid-dimensions]] — how `position` / `span` map to size; grid modes
|
|
93
95
|
- [[group-def]] — containers for controls
|
|
94
96
|
- [[layout-config]] — the top-level structure
|
|
95
97
|
- [[theming]] — Theme system & live theme builder
|
|
@@ -43,8 +43,12 @@ fields:
|
|
|
43
43
|
description: Rotates the arc start position in degrees
|
|
44
44
|
- name: arcThickness
|
|
45
45
|
type: number
|
|
46
|
-
default:
|
|
47
|
-
description: Arc stroke width in points
|
|
46
|
+
default: auto
|
|
47
|
+
description: Arc stroke width in points. Omit to auto-scale with the gauge's size.
|
|
48
|
+
- name: hideValue
|
|
49
|
+
type: bool
|
|
50
|
+
default: false
|
|
51
|
+
description: Show just the arc — hide the center value
|
|
48
52
|
themeFields:
|
|
49
53
|
- name: cornerRadius
|
|
50
54
|
type: number
|
|
@@ -106,10 +110,13 @@ Inherits all [[shared-properties]]. Key fields:
|
|
|
106
110
|
| `step` | number | `1` | Used to determine decimal formatting of center value |
|
|
107
111
|
| `arcAngle` | number | `180` | Arc sweep in degrees (1-360) |
|
|
108
112
|
| `arcRotation` | number | `0` | Rotates the arc start position in degrees |
|
|
109
|
-
| `arcThickness` | number |
|
|
113
|
+
| `arcThickness` | number | auto | Arc stroke width in points. Omit to auto-scale with the gauge's size; set for an absolute width |
|
|
114
|
+
| `hideValue` | bool | `false` | Show just the arc — hide the center value (pairs with `hideBackground` for a compact glyph) |
|
|
110
115
|
|
|
111
116
|
> **Note:** `gaugeStyle` is a shorthand alias. `"half"` sets `arcAngle: 180` and `"full"` sets `arcAngle: 360`. When `arcAngle` is set explicitly, it takes precedence over `gaugeStyle`.
|
|
112
117
|
|
|
118
|
+
> **Sizing:** the arc, stroke, and center value all scale to the gauge's size. A `half` gauge is wide (2:1); a `full` gauge is square (1:1) — give it ~3 `rowSpan` in a 2-D grid so it reads square. See [[grid-dimensions]].
|
|
119
|
+
|
|
113
120
|
## Gauge Segments
|
|
114
121
|
|
|
115
122
|
Color zones that change based on the current value:
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: grid-dimensions
|
|
3
|
+
label: Grid Dimensions
|
|
4
|
+
icon: square.grid.3x3
|
|
5
|
+
category: models
|
|
6
|
+
fields:
|
|
7
|
+
- name: columns
|
|
8
|
+
type: number
|
|
9
|
+
description: Number of columns the grid is divided into (required)
|
|
10
|
+
- name: rows
|
|
11
|
+
type: number
|
|
12
|
+
description: Number of rows the grid is divided into (required)
|
|
13
|
+
- name: mode
|
|
14
|
+
type: string
|
|
15
|
+
description: '"grid" (2-D, default) or "flow" (legacy row-banded)'
|
|
16
|
+
- name: rowHeight
|
|
17
|
+
type: number
|
|
18
|
+
description: Points per row-unit in 2-D mode (default 56)
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
Every tab and every [[group-def|group]] lays its children out on a **grid** of
|
|
22
|
+
`columns` × `rows`. A child declares where it sits with `position: [row, col]`
|
|
23
|
+
and how many cells it covers with `span: [rowSpan, colSpan]` (see [[control-def]]).
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
"grid": { "columns": 4, "rows": 6 }
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Modes
|
|
30
|
+
|
|
31
|
+
| `mode` | Layout | Best for |
|
|
32
|
+
|--------|--------|----------|
|
|
33
|
+
| `"grid"` *(default)* | **True 2-D.** Each child occupies a real `row × col` rectangle. Columns divide the width evenly; rows are a fixed `rowHeight` unit. A child can span **both** axes, so a tall control can sit **beside** two stacked shorter ones (masonry / L-shapes). | Dashboards and mixed screens — gauges, rings, inputs, maps together. |
|
|
34
|
+
| `"flow"` | **Legacy row-banded.** Children are grouped by their row index into horizontal bands; `colSpan` sets proportional width but heights are natural (or `controlHeight`). `rowSpan` is ignored. | Full-page content (a single `map` / `chat` / `cardList` filling the tab) and pure forms where natural heights read best. |
|
|
35
|
+
|
|
36
|
+
`mode` is optional — omit it for the default 2-D grid. Set `"flow"` to opt a tab
|
|
37
|
+
or group out.
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
"grid": { "columns": 4, "rows": 8, "mode": "flow" }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## How spans map to size
|
|
44
|
+
|
|
45
|
+
| | 2-D (`"grid"`) | `"flow"` |
|
|
46
|
+
|--|----------------|----------|
|
|
47
|
+
| **`colSpan`** (width) | `colSpan / columns` of the row width | `colSpan / columns` of the row width |
|
|
48
|
+
| **`rowSpan`** (height) | `rowSpan × rowHeight` points | ignored — height is natural / `controlHeight` |
|
|
49
|
+
|
|
50
|
+
So in 2-D a 1×1 cell is genuinely small, and you make a control bigger by spanning
|
|
51
|
+
more cells. Visual controls scale all of their internals (stroke, glyph, font) to
|
|
52
|
+
the cell they're given — a small ring is tight and legible, a large ring is bold.
|
|
53
|
+
|
|
54
|
+
## `rowHeight`
|
|
55
|
+
|
|
56
|
+
The height of one row-unit, in points (2-D mode only; default **56**). One unit is
|
|
57
|
+
a comfortable input row, so:
|
|
58
|
+
|
|
59
|
+
- inputs (`slider`, `toggle`, `segmentedControl`, …) sit in **1 row**,
|
|
60
|
+
- a square control (`progressRing`, full `gauge`) wants **~3 rows** to read square,
|
|
61
|
+
- tall content (`map`, `list`, `graph`, `image`) spans **as many rows as you give it**.
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
"grid": { "columns": 4, "rows": 10, "rowHeight": 64 }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Sizing a control
|
|
68
|
+
|
|
69
|
+
You usually don't set a height at all — in 2-D the cell (`rowSpan × rowHeight`)
|
|
70
|
+
*is* the height, and in flow shaped controls auto-size to their natural aspect.
|
|
71
|
+
`controlHeight` (see [[control-def]]) is an **override** for the rare case you want
|
|
72
|
+
an exact point height regardless of the grid.
|
|
73
|
+
|
|
74
|
+
## Example — a 2-D dashboard with an L-shape
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"grid": { "columns": 4, "rows": 5 },
|
|
79
|
+
"children": [
|
|
80
|
+
{ "type": "progressRing", "id": "cpu", "position": [0, 0], "span": [3, 2], "label": "CPU" },
|
|
81
|
+
{ "type": "gauge", "id": "mem", "position": [0, 2], "span": [2, 2], "label": "Memory" },
|
|
82
|
+
{ "type": "statusLight", "id": "net", "position": [2, 2], "span": [1, 2], "label": "Uplink" }
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The CPU ring is a tall 2×2-plus block on the left; Memory and the Uplink light
|
|
88
|
+
stack to its right — placement that the flow renderer can't express.
|
|
89
|
+
|
|
90
|
+
## Related
|
|
91
|
+
- [[control-def]] — `position`, `span`, `controlHeight`, `hideValue`, `hideBackground`
|
|
92
|
+
- [[group-def]] — a group has its own grid (and its own `mode`)
|
|
93
|
+
- [[layout-config]] — the top-level structure
|
|
@@ -125,6 +125,11 @@ A layout JSON file has this shape:
|
|
|
125
125
|
}
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
+
Each tab lays its children out on a **2-D grid** — a control's `position` and `span`
|
|
129
|
+
place it in a `row × col` rectangle, so a tall control can sit beside two stacked
|
|
130
|
+
shorter ones. Set a grid's `mode` to `"flow"` for the simpler row-banded layout
|
|
131
|
+
(full-page content, plain forms). See [[grid-dimensions]].
|
|
132
|
+
|
|
128
133
|
Tap any node in the graph to explore the full documentation for each control type, system feature, and data model.
|
|
129
134
|
|
|
130
135
|
---
|
|
@@ -133,9 +138,9 @@ Tap any node in the graph to explore the full documentation for each control typ
|
|
|
133
138
|
|
|
134
139
|
1. **Explore the docs** — tap nodes in the graph to learn about each control
|
|
135
140
|
2. **Write a layout** — create a JSON file following the [[layout-config]] schema
|
|
136
|
-
3. **Run a server** —
|
|
141
|
+
3. **Run a server** — drive your layout from Python with `pip install carterkit`, or
|
|
142
|
+
speak the MeshSocket protocol directly from any language
|
|
137
143
|
4. **Connect** — load your layout and watch it come alive
|
|
138
144
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
[github.com/carterbeaudoin/MeshSocket](https://github.com/carterbeaudoin/MeshSocket)
|
|
145
|
+
Full developer docs — building servers, the wire protocol, and the `carterkit`
|
|
146
|
+
library — live at **carterbeaudoin.net/CAR-TER**.
|
|
@@ -71,7 +71,7 @@ Inherits all [[shared-properties]]. Key fields:
|
|
|
71
71
|
| `mapStyle` | string | `"standard"` | `"standard"`, `"satellite"`, `"hybrid"` |
|
|
72
72
|
| `mapInteractive` | bool | `true` | Allow user pan/zoom |
|
|
73
73
|
| `mapZoom` | number | `0.01` | Default coordinate span in degrees |
|
|
74
|
-
| `controlHeight` | number | `120` (min) |
|
|
74
|
+
| `controlHeight` | number | `120` (min) | In a 2-D grid, give the map more `rowSpan` for a taller map (height = `rowSpan × rowHeight`). In a `flow` grid it is ~120pt by default — set this (e.g. `300`) for a full-screen-feel map. See [[grid-dimensions]]. |
|
|
75
75
|
| `label` | string | — | Header label above the map |
|
|
76
76
|
| `tint` | string | `"#667eea"` | Center point marker color |
|
|
77
77
|
|
|
@@ -28,6 +28,10 @@ fields:
|
|
|
28
28
|
- name: icon
|
|
29
29
|
type: string
|
|
30
30
|
description: SF Symbol in center (ring only)
|
|
31
|
+
- name: hideValue
|
|
32
|
+
type: bool
|
|
33
|
+
default: false
|
|
34
|
+
description: Show just the arc — hide the center value
|
|
31
35
|
themeFields:
|
|
32
36
|
- name: cornerRadius
|
|
33
37
|
type: number
|
|
@@ -85,11 +89,15 @@ Inherits all [[shared-properties]]. Key fields:
|
|
|
85
89
|
| `tint` | string | `"#667eea"` | Fill color |
|
|
86
90
|
| `label` | string | — | Center text (ring) or header text (bar) |
|
|
87
91
|
| `icon` | string | — | SF Symbol in center (ring only) |
|
|
92
|
+
| `hideValue` | bool | `false` | Show just the arc — hide the center value (pairs with `hideBackground` for a compact glyph) |
|
|
88
93
|
|
|
89
94
|
## Styles
|
|
90
95
|
|
|
91
96
|
### `"ring"` (default)
|
|
92
|
-
Circular progress ring with percentage and optional label/icon in the center.
|
|
97
|
+
Circular progress ring with percentage and optional label/icon in the center. The
|
|
98
|
+
stroke, number, icon, and label all scale to the ring's size, so a small cell stays
|
|
99
|
+
tight and a large one reads bold. Square aspect (1:1) — give it ~3 `rowSpan` in a
|
|
100
|
+
2-D grid. Set `hideValue` (and `hideBackground`) for a bare ring glyph. See [[grid-dimensions]].
|
|
93
101
|
|
|
94
102
|
### `"bar"`
|
|
95
103
|
Linear progress bar (thin horizontal bar). Header shows label and percentage.
|
|
@@ -37,4 +37,4 @@ Your use is also governed by our **Privacy Policy** at https://carterbeaudoin.ne
|
|
|
37
37
|
|
|
38
38
|
## 6. Changes and contact
|
|
39
39
|
|
|
40
|
-
We may update these Terms; the effective date reflects the latest version. Questions: support@carterbeaudoin.
|
|
40
|
+
We may update these Terms; the effective date reflects the latest version. Questions: support@carterbeaudoin.net
|