ui-design-workbench-cli 0.5.0__py3-none-any.whl

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 (73) hide show
  1. android_resource_resolver.py +370 -0
  2. android_xml_support.py +230 -0
  3. apple_resource_resolver.py +141 -0
  4. coverage_report.py +134 -0
  5. fidelity_adapter_api.py +89 -0
  6. fidelity_adapters.py +508 -0
  7. fidelity_core.py +241 -0
  8. fidelity_platform_adapters.py +902 -0
  9. ir_contracts.py +76 -0
  10. native_render_android.py +160 -0
  11. native_render_apple.py +165 -0
  12. native_render_contracts.py +148 -0
  13. native_render_registry.py +86 -0
  14. quality_common.py +217 -0
  15. render_preview.py +1797 -0
  16. scan_ui.py +1485 -0
  17. scoped_context.py +458 -0
  18. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/SKILL.md +52 -0
  19. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/adapter-api.md +20 -0
  20. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/agent-integrations.md +39 -0
  21. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/cache-protocol.md +61 -0
  22. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/design-modes.md +109 -0
  23. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/fidelity.md +53 -0
  24. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/ir-schema.md +412 -0
  25. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/native-rendering.md +40 -0
  26. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/platform-profiles.json +84 -0
  27. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/platform-standards.md +131 -0
  28. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/platforms.md +40 -0
  29. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/quality-automation.md +77 -0
  30. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/review-workflow.md +135 -0
  31. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/ui-guidance-mode.md +22 -0
  32. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/ui-policy-schema.md +83 -0
  33. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/ui-reviewer.md +175 -0
  34. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/validation.md +34 -0
  35. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/workbench-ui.md +108 -0
  36. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/schemas/native-render-state.schema.json +71 -0
  37. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/schemas/ui-agent-job.schema.json +36 -0
  38. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/schemas/ui-graph.schema.json +46 -0
  39. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/schemas/ui-ir.patch.schema.json +36 -0
  40. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/schemas/ui-ir.schema.json +296 -0
  41. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/schemas/uidw-config.schema.json +68 -0
  42. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/android_resource_resolver.py +370 -0
  43. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/android_xml_support.py +230 -0
  44. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/apple_resource_resolver.py +141 -0
  45. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/coverage_report.py +134 -0
  46. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/fidelity_adapter_api.py +89 -0
  47. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/fidelity_adapters.py +508 -0
  48. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/fidelity_core.py +241 -0
  49. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/fidelity_platform_adapters.py +902 -0
  50. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/generate_interaction_matrix.py +166 -0
  51. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/ir_contracts.py +76 -0
  52. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/merge_review_state.py +267 -0
  53. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/native_render_android.py +160 -0
  54. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/native_render_apple.py +165 -0
  55. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/native_render_contracts.py +148 -0
  56. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/native_render_registry.py +86 -0
  57. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/quality_common.py +217 -0
  58. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/render_preview.py +1797 -0
  59. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/scan_ui.py +1485 -0
  60. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/scoped_context.py +458 -0
  61. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/smoke_preview.js +978 -0
  62. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/uidw.py +2821 -0
  63. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/uidw_mcp.py +282 -0
  64. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/validate_platform_profiles.py +93 -0
  65. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/visual_regression.py +141 -0
  66. ui_design_workbench_cli-0.5.0.dist-info/METADATA +133 -0
  67. ui_design_workbench_cli-0.5.0.dist-info/RECORD +73 -0
  68. ui_design_workbench_cli-0.5.0.dist-info/WHEEL +5 -0
  69. ui_design_workbench_cli-0.5.0.dist-info/entry_points.txt +3 -0
  70. ui_design_workbench_cli-0.5.0.dist-info/top_level.txt +20 -0
  71. uidw.py +2821 -0
  72. uidw_mcp.py +282 -0
  73. validate_platform_profiles.py +93 -0
fidelity_core.py ADDED
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/env python3
2
+ """Deterministic fidelity primitives shared by scanners, adapters and CLI gates."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import copy
7
+ import hashlib
8
+ import json
9
+ import re
10
+ from dataclasses import dataclass
11
+ from typing import Any, Iterable
12
+
13
+
14
+ FIDELITY_SCHEMA_VERSION = "0.3"
15
+ CONFIDENCE_LEVELS = {"exact", "high", "approximate", "unsupported"}
16
+ NODE_TYPES = {"container", "card", "text", "button", "input", "image", "icon", "spacer", "divider", "list", "custom"}
17
+ BASELINE_FIELDS = ("screens", "screenTree", "nodes", "tokens", "themes", "scenarioFixtures")
18
+ VISUAL_PREFIXES = ("layout.", "style.", "text", "asset", "component", "action")
19
+
20
+
21
+ def canonical_json(value: Any) -> str:
22
+ return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
23
+
24
+
25
+ def stable_id(*parts: Any, prefix: str = "evidence") -> str:
26
+ digest = hashlib.sha256("\x1f".join(str(part) for part in parts).encode("utf-8")).hexdigest()[:16]
27
+ return f"{prefix}-{digest}"
28
+
29
+
30
+ def property_evidence(file: str, line: int, expression: str, adapter: str, confidence: str = "exact") -> dict[str, Any]:
31
+ return {
32
+ "id": stable_id(file, line, expression, adapter),
33
+ "kind": "source",
34
+ "file": file,
35
+ "line": max(1, int(line or 1)),
36
+ "expression": expression.strip()[:500],
37
+ "adapter": adapter,
38
+ "confidence": confidence if confidence in CONFIDENCE_LEVELS else "approximate",
39
+ }
40
+
41
+
42
+ def baseline_payload(ir: dict[str, Any]) -> dict[str, Any]:
43
+ return {field: copy.deepcopy(ir.get(field, {} if field in {"nodes", "tokens", "themes", "scenarioFixtures"} else [])) for field in BASELINE_FIELDS}
44
+
45
+
46
+ def baseline_hash(ir: dict[str, Any]) -> str:
47
+ return "sha256:" + hashlib.sha256(canonical_json(baseline_payload(ir)).encode("utf-8")).hexdigest()
48
+
49
+
50
+ def seal_baseline(ir: dict[str, Any]) -> str:
51
+ review = ir.setdefault("review", {})
52
+ versions = review.setdefault("versions", [])
53
+ baseline_id = str(review.get("baselineVersion") or "baseline")
54
+ review["baselineVersion"] = baseline_id
55
+ baseline = next((item for item in versions if isinstance(item, dict) and item.get("id") == baseline_id), None)
56
+ if baseline is None:
57
+ baseline = {"id": baseline_id, "label": "Before", "kind": "baseline", "status": "approved", "nodeOverrides": {}}
58
+ versions.insert(0, baseline)
59
+ baseline["kind"] = "baseline"
60
+ baseline["nodeOverrides"] = {}
61
+ digest = baseline_hash(ir)
62
+ review["baselineHash"] = digest
63
+ review["baselineHashAlgorithm"] = "sha256"
64
+ return digest
65
+
66
+
67
+ def verify_baseline(ir: dict[str, Any]) -> tuple[bool, str, str]:
68
+ expected = str(ir.get("review", {}).get("baselineHash") or "")
69
+ actual = baseline_hash(ir)
70
+ return bool(expected) and expected == actual, expected, actual
71
+
72
+
73
+ @dataclass(frozen=True)
74
+ class TokenDiagnostic:
75
+ token: str
76
+ code: str
77
+ detail: str
78
+
79
+
80
+ class TokenResolver:
81
+ """Resolve nested `$group.token` aliases without erasing the original references."""
82
+
83
+ _REFERENCE = re.compile(r"^\$([A-Za-z0-9_.-]+)$")
84
+
85
+ def __init__(self, tokens: dict[str, Any], overrides: dict[str, Any] | None = None):
86
+ self.tokens = copy.deepcopy(tokens or {})
87
+ self.overrides = copy.deepcopy(overrides or {})
88
+ self.diagnostics: list[TokenDiagnostic] = []
89
+ self._flat = self._flatten(self.tokens)
90
+ self._flat.update(self._flatten(self.overrides))
91
+
92
+ @staticmethod
93
+ def _flatten(value: dict[str, Any], prefix: str = "") -> dict[str, Any]:
94
+ result: dict[str, Any] = {}
95
+ for key, item in value.items():
96
+ path = f"{prefix}.{key}" if prefix else str(key)
97
+ if isinstance(item, dict) and "value" not in item:
98
+ result.update(TokenResolver._flatten(item, path))
99
+ else:
100
+ result[path] = item.get("value") if isinstance(item, dict) and "value" in item else item
101
+ return result
102
+
103
+ def resolve(self, value: Any) -> Any:
104
+ return self._resolve(value, ())
105
+
106
+ def _resolve(self, value: Any, stack: tuple[str, ...]) -> Any:
107
+ if not isinstance(value, str):
108
+ return value
109
+ match = self._REFERENCE.match(value.strip())
110
+ if not match:
111
+ return value
112
+ name = match.group(1)
113
+ if name in stack:
114
+ self.diagnostics.append(TokenDiagnostic(name, "token-cycle", " -> ".join((*stack, name))))
115
+ return value
116
+ if name not in self._flat:
117
+ self.diagnostics.append(TokenDiagnostic(name, "token-missing", f"No token named {name}"))
118
+ return value
119
+ return self._resolve(self._flat[name], (*stack, name))
120
+
121
+ def resolved_tokens(self) -> dict[str, Any]:
122
+ return {name: self._resolve(value, (name,)) for name, value in sorted(self._flat.items())}
123
+
124
+
125
+ def _path_exists(node: dict[str, Any], path: str) -> bool:
126
+ current: Any = node
127
+ for part in path.split("."):
128
+ if not isinstance(current, dict) or part not in current:
129
+ return False
130
+ current = current[part]
131
+ return True
132
+
133
+
134
+ def _property_paths(node: dict[str, Any]) -> Iterable[str]:
135
+ for group in ("layout", "style"):
136
+ for key in node.get(group, {}) if isinstance(node.get(group), dict) else ():
137
+ yield f"{group}.{key}"
138
+ for key in ("text", "asset", "component", "action"):
139
+ if key in node:
140
+ yield key
141
+
142
+
143
+ def validate_strict_ir(ir: dict[str, Any]) -> list[str]:
144
+ """Semantic v0.3 validator. It is strict only for fidelity schema 0.3 artifacts."""
145
+ if str(ir.get("fidelity", {}).get("schemaVersion") or "") != FIDELITY_SCHEMA_VERSION:
146
+ return []
147
+ errors: list[str] = []
148
+ nodes = ir.get("nodes")
149
+ screens = ir.get("screens")
150
+ if not isinstance(nodes, dict) or not nodes:
151
+ return ["Fidelity 0.3 IR requires non-empty nodes"]
152
+ if not isinstance(screens, list) or not screens:
153
+ errors.append("Fidelity 0.3 IR requires non-empty screens")
154
+ screens = []
155
+ root_ids = {screen.get("root") for screen in screens if isinstance(screen, dict)}
156
+ for root_id in sorted(root_ids):
157
+ if root_id not in nodes:
158
+ errors.append(f"Screen root does not exist: {root_id}")
159
+ for node_id, node in nodes.items():
160
+ if not isinstance(node, dict):
161
+ errors.append(f"Node {node_id} must be an object")
162
+ continue
163
+ if node.get("type") not in NODE_TYPES:
164
+ errors.append(f"Node {node_id} has unsupported type {node.get('type')}")
165
+ confidence = node.get("confidence")
166
+ if confidence not in CONFIDENCE_LEVELS:
167
+ errors.append(f"Node {node_id} needs exact/high/approximate/unsupported confidence")
168
+ provenance = node.get("provenance", {})
169
+ if not isinstance(provenance, dict):
170
+ errors.append(f"Node {node_id} provenance must be an object")
171
+ provenance = {}
172
+ for path, evidence in provenance.items():
173
+ if not _path_exists(node, path):
174
+ errors.append(f"Node {node_id} provenance references missing property {path}")
175
+ if not isinstance(evidence, dict) or not all(evidence.get(key) not in (None, "") for key in ("id", "kind", "file", "line", "adapter", "confidence")):
176
+ errors.append(f"Node {node_id} has invalid provenance for {path}")
177
+ elif evidence.get("confidence") not in CONFIDENCE_LEVELS:
178
+ errors.append(f"Node {node_id} provenance {path} has invalid confidence")
179
+ if confidence != "unsupported":
180
+ for path in _property_paths(node):
181
+ if path.startswith(VISUAL_PREFIXES) and path not in provenance:
182
+ errors.append(f"Node {node_id} property {path} lacks provenance")
183
+ for child in node.get("children", []):
184
+ if child not in nodes:
185
+ errors.append(f"Node {node_id} references missing child {child}")
186
+ resolver = TokenResolver(ir.get("tokens", {}))
187
+ for node in nodes.values():
188
+ if not isinstance(node, dict):
189
+ continue
190
+ for group in ("layout", "style"):
191
+ for value in node.get(group, {}).values() if isinstance(node.get(group), dict) else ():
192
+ resolver.resolve(value)
193
+ errors.extend(f"{item.code}: {item.detail}" for item in resolver.diagnostics)
194
+ for theme in ir.get("themes", {}).get("items", []) if isinstance(ir.get("themes"), dict) else ():
195
+ if not isinstance(theme, dict):
196
+ continue
197
+ theme_resolver = TokenResolver(ir.get("tokens", {}), theme.get("tokenOverrides", {}))
198
+ theme_resolver.resolved_tokens()
199
+ errors.extend(f"theme {theme.get('id')}: {item.code}: {item.detail}" for item in theme_resolver.diagnostics)
200
+ valid_baseline, expected, actual = verify_baseline(ir)
201
+ if not valid_baseline:
202
+ errors.append(f"Immutable baseline hash mismatch: expected {expected or '<missing>'}, actual {actual}")
203
+ return errors
204
+
205
+
206
+ def fidelity_report(ir: dict[str, Any]) -> dict[str, Any]:
207
+ schema_version = str(ir.get("fidelity", {}).get("schemaVersion") or "")
208
+ applicable = schema_version == FIDELITY_SCHEMA_VERSION
209
+ errors = validate_strict_ir(ir)
210
+ properties = 0
211
+ sourced = 0
212
+ by_confidence = {level: 0 for level in sorted(CONFIDENCE_LEVELS)}
213
+ unsupported: list[dict[str, Any]] = []
214
+ for node_id, node in ir.get("nodes", {}).items():
215
+ if not isinstance(node, dict):
216
+ continue
217
+ by_confidence[str(node.get("confidence") or "unsupported")] = by_confidence.get(str(node.get("confidence") or "unsupported"), 0) + 1
218
+ provenance = node.get("provenance", {}) if isinstance(node.get("provenance"), dict) else {}
219
+ for path in _property_paths(node):
220
+ properties += 1
221
+ sourced += int(path in provenance)
222
+ if node.get("confidence") == "unsupported":
223
+ unsupported.append({"nodeId": node_id, "component": node.get("component"), "source": node.get("source", {})})
224
+ baseline_valid, baseline_expected, baseline_actual = verify_baseline(ir)
225
+ fidelity_errors = [
226
+ *errors,
227
+ *(f"Node {item['nodeId']} is an unsupported placeholder" for item in unsupported),
228
+ ]
229
+ return {
230
+ "version": 1,
231
+ "schemaVersion": schema_version or None,
232
+ "applicable": applicable,
233
+ "status": "not-applicable" if not applicable else "pass" if not fidelity_errors else "fail",
234
+ "applicabilityReason": None if applicable else f"Fidelity Core requires schema {FIDELITY_SCHEMA_VERSION}",
235
+ "strictErrors": errors,
236
+ "fidelityErrors": fidelity_errors,
237
+ "propertyProvenance": {"covered": sourced, "total": properties, "percent": round(100 * sourced / properties, 1) if properties else 100.0},
238
+ "nodeConfidence": by_confidence,
239
+ "unsupported": unsupported,
240
+ "baseline": {"valid": baseline_valid, "expected": baseline_expected, "actual": baseline_actual},
241
+ }