ourui 0.2.0__tar.gz → 0.2.2__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.
- {ourui-0.2.0 → ourui-0.2.2}/PKG-INFO +2 -2
- {ourui-0.2.0 → ourui-0.2.2}/README.md +1 -1
- ourui-0.2.2/ourui/design/__init__.py +5 -0
- ourui-0.2.2/ourui/design/resolve.py +121 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/emit/html.py +72 -5
- {ourui-0.2.0 → ourui-0.2.2}/ourui/pipeline.py +13 -2
- {ourui-0.2.0 → ourui-0.2.2}/ourui.egg-info/PKG-INFO +2 -2
- {ourui-0.2.0 → ourui-0.2.2}/ourui.egg-info/SOURCES.txt +2 -0
- {ourui-0.2.0 → ourui-0.2.2}/pyproject.toml +1 -1
- {ourui-0.2.0 → ourui-0.2.2}/LICENSE +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/__init__.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/analysis/__init__.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/analysis/components.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/cli.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/emit/__init__.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/emit/js.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/lowering/__init__.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/lowering/intent.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/lowering/layout.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/lowering/presentation.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/lowering/render.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/lsp/__init__.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/lsp/completions.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/lsp/protocol.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/lsp/server.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/node.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/parse/__init__.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/runtime/__init__.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/runtime/hmr.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/runtime/invoke.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/runtime/session.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/serialize/__init__.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/theme.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui/ui.py +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui.egg-info/dependency_links.txt +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui.egg-info/entry_points.txt +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/ourui.egg-info/top_level.txt +0 -0
- {ourui-0.2.0 → ourui-0.2.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ourui
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Python-first UI compiler — write intent in Python, emit HTML/CSS/JS
|
|
5
5
|
Author: OurUI contributors
|
|
6
6
|
License: MIT License
|
|
@@ -46,7 +46,7 @@ Description-Content-Type: text/markdown
|
|
|
46
46
|
License-File: LICENSE
|
|
47
47
|
Dynamic: license-file
|
|
48
48
|
|
|
49
|
-
# ourui 0.2.
|
|
49
|
+
# ourui 0.2.2
|
|
50
50
|
|
|
51
51
|
Python package for the **OurUI** compiler and runtime.
|
|
52
52
|
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Design System resolution: Presentation Graph + pack → Resolved Design (RFC-002)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ourui.theme import DEFAULT_DARK, DEFAULT_LIGHT
|
|
9
|
+
|
|
10
|
+
PACK_ID = "ourui-default"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def default_pack() -> dict[str, Any]:
|
|
14
|
+
"""Seed Design System pack from current theme tables (provisional migration)."""
|
|
15
|
+
return {
|
|
16
|
+
"id": PACK_ID,
|
|
17
|
+
"modes": {
|
|
18
|
+
"light": dict(DEFAULT_LIGHT),
|
|
19
|
+
"dark": dict(DEFAULT_DARK),
|
|
20
|
+
},
|
|
21
|
+
"control": {
|
|
22
|
+
"pad_block": "space_sm",
|
|
23
|
+
"pad_inline": "space_md",
|
|
24
|
+
"radius": "radius",
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class ResolvedDesign:
|
|
31
|
+
pack: str
|
|
32
|
+
mode: str
|
|
33
|
+
nodes: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
34
|
+
tokens: dict[str, dict[str, str]] = field(default_factory=dict)
|
|
35
|
+
|
|
36
|
+
def to_dict(self) -> dict[str, Any]:
|
|
37
|
+
return {
|
|
38
|
+
"pack": self.pack,
|
|
39
|
+
"mode": self.mode,
|
|
40
|
+
"nodes": {nid: self.nodes[nid] for nid in sorted(self.nodes)},
|
|
41
|
+
"tokens": {
|
|
42
|
+
"light": dict(self.tokens.get("light", {})),
|
|
43
|
+
"dark": dict(self.tokens.get("dark", {})),
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _tone_pair(tone: str | None, mode_tokens: dict[str, str]) -> dict[str, str]:
|
|
49
|
+
if not tone:
|
|
50
|
+
return {
|
|
51
|
+
"fill": mode_tokens.get("muted", ""),
|
|
52
|
+
"fg": mode_tokens.get("muted_fg", ""),
|
|
53
|
+
}
|
|
54
|
+
fill_key = tone
|
|
55
|
+
fg_key = f"{tone}_fg"
|
|
56
|
+
if fill_key not in mode_tokens:
|
|
57
|
+
fill_key = "primary"
|
|
58
|
+
fg_key = "primary_fg"
|
|
59
|
+
return {
|
|
60
|
+
"fill": mode_tokens.get(fill_key, mode_tokens.get("primary", "")),
|
|
61
|
+
"fg": mode_tokens.get(fg_key, mode_tokens.get("primary_fg", "")),
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def resolve_design(
|
|
66
|
+
presentation_graph: Any,
|
|
67
|
+
*,
|
|
68
|
+
pack: dict[str, Any] | None = None,
|
|
69
|
+
mode: str = "light",
|
|
70
|
+
token_overrides: dict[str, dict[str, str]] | None = None,
|
|
71
|
+
) -> ResolvedDesign:
|
|
72
|
+
"""Pure resolution: PG + Design System pack → Resolved Design (no CSS)."""
|
|
73
|
+
pack = pack or default_pack()
|
|
74
|
+
modes = {
|
|
75
|
+
"light": dict(pack.get("modes", {}).get("light", DEFAULT_LIGHT)),
|
|
76
|
+
"dark": dict(pack.get("modes", {}).get("dark", DEFAULT_DARK)),
|
|
77
|
+
}
|
|
78
|
+
if token_overrides:
|
|
79
|
+
for m in ("light", "dark"):
|
|
80
|
+
if m in token_overrides:
|
|
81
|
+
modes[m].update(token_overrides[m])
|
|
82
|
+
|
|
83
|
+
mode_key = mode if mode in modes else "light"
|
|
84
|
+
mode_tokens = modes[mode_key]
|
|
85
|
+
control = pack.get("control") or {}
|
|
86
|
+
pad_block = mode_tokens.get(control.get("pad_block", "space_sm"), mode_tokens.get("space_sm", ""))
|
|
87
|
+
pad_inline = mode_tokens.get(control.get("pad_inline", "space_md"), mode_tokens.get("space_md", ""))
|
|
88
|
+
radius = mode_tokens.get(control.get("radius", "radius"), mode_tokens.get("radius", ""))
|
|
89
|
+
|
|
90
|
+
pg = presentation_graph.to_dict() if hasattr(presentation_graph, "to_dict") else dict(presentation_graph)
|
|
91
|
+
nodes_in = pg.get("nodes") or {}
|
|
92
|
+
|
|
93
|
+
out = ResolvedDesign(pack=str(pack.get("id", PACK_ID)), mode=mode_key, tokens=modes)
|
|
94
|
+
for nid, node in nodes_in.items():
|
|
95
|
+
tone = node.get("tone")
|
|
96
|
+
if isinstance(tone, str):
|
|
97
|
+
pair = _tone_pair(tone, mode_tokens)
|
|
98
|
+
else:
|
|
99
|
+
pair = _tone_pair(None, mode_tokens)
|
|
100
|
+
resolved: dict[str, Any] = {
|
|
101
|
+
"fill": pair["fill"],
|
|
102
|
+
"fg": pair["fg"],
|
|
103
|
+
"radius": radius,
|
|
104
|
+
"pad_block": pad_block,
|
|
105
|
+
"pad_inline": pad_inline,
|
|
106
|
+
}
|
|
107
|
+
entry = {
|
|
108
|
+
"id": nid,
|
|
109
|
+
"kind": node.get("kind"),
|
|
110
|
+
"role": node.get("role"),
|
|
111
|
+
"resolved": resolved,
|
|
112
|
+
"provenance": [*node.get("provenance", []), "resolve:design"],
|
|
113
|
+
}
|
|
114
|
+
if tone is not None:
|
|
115
|
+
entry["tone"] = tone
|
|
116
|
+
if "href" in node:
|
|
117
|
+
entry["href"] = node["href"]
|
|
118
|
+
if "shell_layout" in node:
|
|
119
|
+
entry["shell_layout"] = node["shell_layout"]
|
|
120
|
+
out.nodes[nid] = entry
|
|
121
|
+
return out
|
|
@@ -98,6 +98,60 @@ button.ourui-tone-muted {
|
|
|
98
98
|
color: var(--ourui-muted-fg);
|
|
99
99
|
}
|
|
100
100
|
"""
|
|
101
|
+
# Tone classes above use CSS vars seeded from Resolved Design (Host Contract).
|
|
102
|
+
# Per-node rules from Resolved Design override for concrete fill/fg/radius/pad.
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _as_resolved_design(resolved_design: Any) -> dict[str, Any] | None:
|
|
106
|
+
if resolved_design is None:
|
|
107
|
+
return None
|
|
108
|
+
if hasattr(resolved_design, "to_dict"):
|
|
109
|
+
return resolved_design.to_dict()
|
|
110
|
+
return dict(resolved_design)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _tokens_from_resolved(rd: dict[str, Any]) -> dict[str, dict[str, str]]:
|
|
114
|
+
tokens = rd.get("tokens") or {}
|
|
115
|
+
return {
|
|
116
|
+
"light": dict(tokens.get("light") or {}),
|
|
117
|
+
"dark": dict(tokens.get("dark") or {}),
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _emit_resolved_node_css(rd: dict[str, Any]) -> str:
|
|
122
|
+
"""Host maps Resolved Design node values → CSS (no Design System tables)."""
|
|
123
|
+
lines: list[str] = []
|
|
124
|
+
nodes = rd.get("nodes") or {}
|
|
125
|
+
for nid in sorted(nodes):
|
|
126
|
+
node = nodes[nid]
|
|
127
|
+
role = node.get("role")
|
|
128
|
+
resolved = node.get("resolved") or {}
|
|
129
|
+
if role not in {"button", "link"}:
|
|
130
|
+
continue
|
|
131
|
+
fill = resolved.get("fill")
|
|
132
|
+
fg = resolved.get("fg")
|
|
133
|
+
if not fill and not fg:
|
|
134
|
+
continue
|
|
135
|
+
lines.append(f'[data-ourui-id="{nid}"] {{')
|
|
136
|
+
if role == "button":
|
|
137
|
+
if fill:
|
|
138
|
+
lines.append(f" background: {fill};")
|
|
139
|
+
if fg:
|
|
140
|
+
lines.append(f" color: {fg};")
|
|
141
|
+
if resolved.get("radius"):
|
|
142
|
+
lines.append(f" border-radius: {resolved['radius']};")
|
|
143
|
+
pad_b = resolved.get("pad_block")
|
|
144
|
+
pad_i = resolved.get("pad_inline")
|
|
145
|
+
if pad_b and pad_i:
|
|
146
|
+
lines.append(f" padding: {pad_b} {pad_i};")
|
|
147
|
+
lines.append(" border-color: transparent;")
|
|
148
|
+
else:
|
|
149
|
+
if fill:
|
|
150
|
+
lines.append(f" color: {fill};")
|
|
151
|
+
lines.append("}")
|
|
152
|
+
if not lines:
|
|
153
|
+
return ""
|
|
154
|
+
return "\n".join(lines) + "\n"
|
|
101
155
|
|
|
102
156
|
|
|
103
157
|
def _tone_name(attrs: dict[str, Any]) -> str | None:
|
|
@@ -232,7 +286,16 @@ def apply_state_values(rtr: dict[str, Any], state_values: dict[str, Any] | None)
|
|
|
232
286
|
return out
|
|
233
287
|
|
|
234
288
|
|
|
235
|
-
def emit_css(
|
|
289
|
+
def emit_css(
|
|
290
|
+
tokens: dict[str, dict[str, str]] | None = None,
|
|
291
|
+
*,
|
|
292
|
+
resolved_design: Any = None,
|
|
293
|
+
) -> str:
|
|
294
|
+
"""Emit host CSS. Prefer Resolved Design tokens + per-node rules (Host Contract)."""
|
|
295
|
+
rd = _as_resolved_design(resolved_design)
|
|
296
|
+
if rd is not None:
|
|
297
|
+
token_block = emit_tokens_css(_tokens_from_resolved(rd))
|
|
298
|
+
return token_block + _BASE_CSS + _emit_resolved_node_css(rd)
|
|
236
299
|
token_block = emit_tokens_css(tokens or default_tokens())
|
|
237
300
|
return token_block + _BASE_CSS
|
|
238
301
|
|
|
@@ -244,8 +307,9 @@ def emit_html_document(
|
|
|
244
307
|
state_values: dict[str, Any] | None = None,
|
|
245
308
|
hmr: bool = False,
|
|
246
309
|
tokens: dict[str, dict[str, str]] | None = None,
|
|
310
|
+
resolved_design: Any = None,
|
|
247
311
|
) -> str:
|
|
248
|
-
"""Emit
|
|
312
|
+
"""Emit HTML from Host Contract inputs: RTR + Resolved Design (+ optional tokens fallback)."""
|
|
249
313
|
rtr = apply_state_values(rtr, state_values)
|
|
250
314
|
nodes = rtr["nodes"]
|
|
251
315
|
roots = rtr["roots"]
|
|
@@ -255,7 +319,7 @@ def emit_html_document(
|
|
|
255
319
|
body_lines.append(" </div>")
|
|
256
320
|
|
|
257
321
|
js = emit_js(rtr, hmr=hmr).rstrip("\n")
|
|
258
|
-
css = emit_css(tokens).rstrip("\n")
|
|
322
|
+
css = emit_css(tokens, resolved_design=resolved_design).rstrip("\n")
|
|
259
323
|
parts = [
|
|
260
324
|
"<!DOCTYPE html>",
|
|
261
325
|
'<html lang="en">',
|
|
@@ -283,10 +347,13 @@ def emit_bundle(
|
|
|
283
347
|
*,
|
|
284
348
|
title: str = "OurUI",
|
|
285
349
|
tokens: dict[str, dict[str, str]] | None = None,
|
|
350
|
+
resolved_design: Any = None,
|
|
286
351
|
) -> dict[str, str]:
|
|
287
352
|
"""Serializable emit artifacts (I10): html + css + js."""
|
|
288
353
|
return {
|
|
289
|
-
"html": emit_html_document(
|
|
290
|
-
|
|
354
|
+
"html": emit_html_document(
|
|
355
|
+
rtr, title=title, tokens=tokens, resolved_design=resolved_design
|
|
356
|
+
),
|
|
357
|
+
"css": emit_css(tokens, resolved_design=resolved_design),
|
|
291
358
|
"js": emit_js(rtr),
|
|
292
359
|
}
|
|
@@ -4,6 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
6
|
from ourui.analysis import build_semantic_graph
|
|
7
|
+
from ourui.design import resolve_design
|
|
7
8
|
from ourui.emit import emit_bundle, emit_html_document
|
|
8
9
|
from ourui.lowering import lower_to_iir, lower_to_ltr, lower_to_presentation_graph, lower_to_rtr
|
|
9
10
|
from ourui.serialize import dumps_deterministic
|
|
@@ -30,6 +31,10 @@ def compile_to_rtr(path: str | Path, *, route: str | None = None) -> dict[str, A
|
|
|
30
31
|
sg.roots = [sg.routes[default_route]]
|
|
31
32
|
iir = lower_to_iir(sg)
|
|
32
33
|
presentation_graph = lower_to_presentation_graph(iir)
|
|
34
|
+
resolved_design = resolve_design(
|
|
35
|
+
presentation_graph,
|
|
36
|
+
token_overrides=sg.tokens,
|
|
37
|
+
)
|
|
33
38
|
ltr = lower_to_ltr(iir)
|
|
34
39
|
rtr = lower_to_rtr(ltr)
|
|
35
40
|
return {
|
|
@@ -38,6 +43,7 @@ def compile_to_rtr(path: str | Path, *, route: str | None = None) -> dict[str, A
|
|
|
38
43
|
"dependency_graph": dg,
|
|
39
44
|
"iir": iir,
|
|
40
45
|
"presentation_graph": presentation_graph,
|
|
46
|
+
"resolved_design": resolved_design,
|
|
41
47
|
"ltr": ltr,
|
|
42
48
|
"rtr": rtr,
|
|
43
49
|
}
|
|
@@ -47,12 +53,13 @@ def compile_dump(path: str | Path) -> dict[str, Any]:
|
|
|
47
53
|
path = Path(path)
|
|
48
54
|
artifacts = compile_to_rtr(path)
|
|
49
55
|
return {
|
|
50
|
-
"version":
|
|
56
|
+
"version": 12,
|
|
51
57
|
"source": artifacts["source"],
|
|
52
58
|
"semantic_graph": artifacts["semantic_graph"].to_dict(),
|
|
53
59
|
"dependency_graph": artifacts["dependency_graph"].to_dict(),
|
|
54
60
|
"iir": artifacts["iir"].to_dict(),
|
|
55
61
|
"presentation_graph": artifacts["presentation_graph"].to_dict(),
|
|
62
|
+
"resolved_design": artifacts["resolved_design"].to_dict(),
|
|
56
63
|
"ltr": artifacts["ltr"].to_dict(),
|
|
57
64
|
"rtr": artifacts["rtr"].to_dict(),
|
|
58
65
|
"emit": {
|
|
@@ -63,6 +70,8 @@ def compile_dump(path: str | Path) -> dict[str, Any]:
|
|
|
63
70
|
"components": True,
|
|
64
71
|
"tokens": True,
|
|
65
72
|
"presentation_graph": True,
|
|
73
|
+
"resolved_design": True,
|
|
74
|
+
"host_contract": True,
|
|
66
75
|
},
|
|
67
76
|
}
|
|
68
77
|
|
|
@@ -79,7 +88,7 @@ def emit_html(
|
|
|
79
88
|
state_values: dict[str, Any] | None = None,
|
|
80
89
|
hmr: bool = False,
|
|
81
90
|
) -> str:
|
|
82
|
-
"""Compile source → RTR → HTML
|
|
91
|
+
"""Compile source → RTR + Resolved Design → HTML (Host Contract)."""
|
|
83
92
|
artifacts = compile_to_rtr(path, route=route)
|
|
84
93
|
doc_title = title or Path(path).stem
|
|
85
94
|
return emit_html_document(
|
|
@@ -88,6 +97,7 @@ def emit_html(
|
|
|
88
97
|
state_values=state_values,
|
|
89
98
|
hmr=hmr,
|
|
90
99
|
tokens=artifacts["semantic_graph"].tokens,
|
|
100
|
+
resolved_design=artifacts["resolved_design"],
|
|
91
101
|
)
|
|
92
102
|
|
|
93
103
|
|
|
@@ -98,4 +108,5 @@ def emit_all(path: str | Path, *, title: str | None = None) -> dict[str, str]:
|
|
|
98
108
|
artifacts["rtr"].to_dict(),
|
|
99
109
|
title=doc_title,
|
|
100
110
|
tokens=artifacts["semantic_graph"].tokens,
|
|
111
|
+
resolved_design=artifacts["resolved_design"],
|
|
101
112
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ourui
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Python-first UI compiler — write intent in Python, emit HTML/CSS/JS
|
|
5
5
|
Author: OurUI contributors
|
|
6
6
|
License: MIT License
|
|
@@ -46,7 +46,7 @@ Description-Content-Type: text/markdown
|
|
|
46
46
|
License-File: LICENSE
|
|
47
47
|
Dynamic: license-file
|
|
48
48
|
|
|
49
|
-
# ourui 0.2.
|
|
49
|
+
# ourui 0.2.2
|
|
50
50
|
|
|
51
51
|
Python package for the **OurUI** compiler and runtime.
|
|
52
52
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|