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.
Files changed (96) hide show
  1. {carterkit-0.3.1 → carterkit-0.5.0}/PKG-INFO +63 -18
  2. carterkit-0.5.0/README.md +141 -0
  3. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/__init__.py +14 -5
  4. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/buffer.py +8 -2
  5. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/control-def.md +5 -3
  6. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/gauge.md +10 -3
  7. carterkit-0.5.0/carterkit/controldocs/grid-dimensions.md +93 -0
  8. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/index.md +9 -4
  9. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/map.md +1 -1
  10. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/privacy.md +1 -1
  11. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/progress-ring.md +9 -1
  12. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/terms.md +1 -1
  13. carterkit-0.5.0/carterkit/declare.py +280 -0
  14. carterkit-0.5.0/carterkit/dynamic.py +99 -0
  15. carterkit-0.5.0/carterkit/layout.py +458 -0
  16. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit.egg-info/PKG-INFO +63 -18
  17. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit.egg-info/SOURCES.txt +6 -0
  18. {carterkit-0.3.1 → carterkit-0.5.0}/pyproject.toml +1 -1
  19. carterkit-0.5.0/tests/test_client.py +153 -0
  20. carterkit-0.5.0/tests/test_declare.py +109 -0
  21. carterkit-0.5.0/tests/test_dynamic.py +67 -0
  22. carterkit-0.5.0/tests/test_ui.py +92 -0
  23. carterkit-0.3.1/README.md +0 -96
  24. carterkit-0.3.1/carterkit/layout.py +0 -87
  25. carterkit-0.3.1/tests/test_client.py +0 -32
  26. {carterkit-0.3.1 → carterkit-0.5.0}/LICENSE +0 -0
  27. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/__main__.py +0 -0
  28. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/bind.py +0 -0
  29. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/catalog.py +0 -0
  30. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/cli.py +0 -0
  31. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/client.py +0 -0
  32. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/codegen.py +0 -0
  33. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/accordion.md +0 -0
  34. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/actions.md +0 -0
  35. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/animations.md +0 -0
  36. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/appearance.md +0 -0
  37. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/button.md +0 -0
  38. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/cardList.md +0 -0
  39. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/carousel.md +0 -0
  40. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/chat.md +0 -0
  41. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/color-picker.md +0 -0
  42. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/date-picker.md +0 -0
  43. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/divider.md +0 -0
  44. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/flip-card.md +0 -0
  45. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/graph.md +0 -0
  46. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/group-def.md +0 -0
  47. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/haptics.md +0 -0
  48. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/image.md +0 -0
  49. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/joystick.md +0 -0
  50. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/label.md +0 -0
  51. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/layout-config.md +0 -0
  52. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/list.md +0 -0
  53. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/log-console.md +0 -0
  54. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/long-press.md +0 -0
  55. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/picker.md +0 -0
  56. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/pulse.md +0 -0
  57. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/qr-code.md +0 -0
  58. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/segmented.md +0 -0
  59. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/slider.md +0 -0
  60. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/spacer.md +0 -0
  61. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/sparkline.md +0 -0
  62. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/status-light.md +0 -0
  63. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/stepper.md +0 -0
  64. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/sync.md +0 -0
  65. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/text-input.md +0 -0
  66. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/theming.md +0 -0
  67. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/toggle.md +0 -0
  68. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/visibility.md +0 -0
  69. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controldocs/web-view.md +0 -0
  70. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/controls.py +0 -0
  71. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/e2ee.py +0 -0
  72. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/grid.py +0 -0
  73. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/infer.py +0 -0
  74. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/py.typed +0 -0
  75. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/theming.py +0 -0
  76. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/tune.py +0 -0
  77. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit/validate.py +0 -0
  78. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit.egg-info/dependency_links.txt +0 -0
  79. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit.egg-info/entry_points.txt +0 -0
  80. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit.egg-info/requires.txt +0 -0
  81. {carterkit-0.3.1 → carterkit-0.5.0}/carterkit.egg-info/top_level.txt +0 -0
  82. {carterkit-0.3.1 → carterkit-0.5.0}/setup.cfg +0 -0
  83. {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_bind.py +0 -0
  84. {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_buffer.py +0 -0
  85. {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_catalog.py +0 -0
  86. {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_cli.py +0 -0
  87. {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_codegen.py +0 -0
  88. {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_controls.py +0 -0
  89. {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_e2ee.py +0 -0
  90. {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_grid.py +0 -0
  91. {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_infer.py +0 -0
  92. {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_layout.py +0 -0
  93. {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_theming.py +0 -0
  94. {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_tune.py +0 -0
  95. {carterkit-0.3.1 → carterkit-0.5.0}/tests/test_validate.py +0 -0
  96. {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.1
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
- The fluent `Layout` builder composes **typed builders** (`build.<control>`) and
55
- **binding helpers** (`bind`) all generated from / shaped by the bundled docs, so
56
- unknown control types and bad enum values raise instead of silently shipping a broken
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, build, bind
61
-
62
- lay = (Layout("Dashboard", columns=4, rows=4)
63
- .connect("ws://192.168.1.50:8765", channel="home")
64
- .tab("Main", icon="gauge")
65
- .add(build.gauge(id="cpu", label="CPU", min=0, max=100,
66
- sync=[bind.listen("cpu", filter={"msg_type": "metrics"})]),
67
- default_span=[2, 2])
68
- .add(build.button(id="refresh", label="Refresh", action=bind.action("refresh"))))
69
-
70
- print(lay.findings()) # schema + grid + binding lint against the bundled catalog
71
- help(build.gauge) # ← prints the gauge documentation, straight from the docs
72
- layout = lay.layout # the composed dict, ready to push/save
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
+ [![PyPI](https://img.shields.io/pypi/v/carterkit.svg)](https://pypi.org/project/carterkit/)
4
+ [![Downloads](https://static.pepy.tech/badge/carterkit)](https://pepy.tech/project/carterkit)
5
+ [![Python versions](https://img.shields.io/pypi/pyversions/carterkit.svg)](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) -> int:
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": {"columns": columns, "rows": rows}, "children": [],
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` | [rows, cols] | `[1, 1]` | Grid cells occupied (column width; **row count is advisory** see `controlHeight`) |
23
- | `controlHeight` | number | — | Explicit rendered height in points. The grid lays controls out as natural-height rows, so tall controls (`map`, `list`, `graph`, `sparkline`) need this to claim vertical space. Opt-in: omit to keep the control's intrinsic height. |
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
- | `hideBackground` | bool | `false` | Remove the glass card background |
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: 8
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 | `8` | Arc stroke width in points |
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** — start a MeshSocket server to handle events
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
- The full MeshSocket server implementation and protocol documentation is available on GitHub:
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) | Rendered height in points. The map is only ~120pt tall by default; set this (e.g. `300`) for a full-screen-feel map. See [[control-def]]. |
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
 
@@ -49,4 +49,4 @@ We may update this policy; the effective date above reflects the latest version.
49
49
 
50
50
  ## Contact
51
51
 
52
- Questions or data requests: support@carterbeaudoin.com
52
+ Questions or data requests: support@carterbeaudoin.net
@@ -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. Square aspect ratio.
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.com
40
+ We may update these Terms; the effective date reflects the latest version. Questions: support@carterbeaudoin.net