@tmorrow/cre8-wc 2.0.4 → 2.0.5

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 (194) hide show
  1. package/a2ui/build-knowledge-graph.py +297 -0
  2. package/a2ui/catalog +240 -0
  3. package/a2ui/catalog-kg.json +6704 -0
  4. package/a2ui/catalog.json +576 -84
  5. package/a2ui/catalog.svg +3295 -0
  6. package/a2ui/demo.html +5 -49
  7. package/a2ui/examples/card-gallery.json +786 -0
  8. package/a2ui/examples/dating-grid.json +456 -0
  9. package/a2ui/examples/llm-observability.json +1719 -0
  10. package/a2ui/examples/portfolio.json +341 -0
  11. package/a2ui/generate-catalog.mjs +3 -0
  12. package/cdn/cre8-wc.esm.js +3057 -2438
  13. package/cdn/cre8-wc.esm.js.map +1 -1
  14. package/cdn/cre8-wc.min.js +1801 -1204
  15. package/cdn/cre8-wc.min.js.map +1 -1
  16. package/lib/components/accordion/accordion.d.ts +1 -0
  17. package/lib/components/accordion/accordion.d.ts.map +1 -1
  18. package/lib/components/accordion/accordion.js +1 -0
  19. package/lib/components/accordion/accordion.js.map +1 -1
  20. package/lib/components/accordion-item/accordion-item.d.ts +1 -0
  21. package/lib/components/accordion-item/accordion-item.d.ts.map +1 -1
  22. package/lib/components/accordion-item/accordion-item.js +1 -0
  23. package/lib/components/accordion-item/accordion-item.js.map +1 -1
  24. package/lib/components/alert/alert.d.ts +6 -0
  25. package/lib/components/alert/alert.d.ts.map +1 -1
  26. package/lib/components/alert/alert.js +6 -0
  27. package/lib/components/alert/alert.js.map +1 -1
  28. package/lib/components/button/button.d.ts +2 -0
  29. package/lib/components/button/button.d.ts.map +1 -1
  30. package/lib/components/button/button.js +2 -0
  31. package/lib/components/button/button.js.map +1 -1
  32. package/lib/components/button/button.styles.d.ts.map +1 -1
  33. package/lib/components/button/button.styles.js +1120 -557
  34. package/lib/components/button/button.styles.js.map +1 -1
  35. package/lib/components/button-group/button-group.d.ts +9 -0
  36. package/lib/components/button-group/button-group.d.ts.map +1 -1
  37. package/lib/components/button-group/button-group.js +8 -0
  38. package/lib/components/button-group/button-group.js.map +1 -1
  39. package/lib/components/button-group/button-group.styles.d.ts.map +1 -1
  40. package/lib/components/button-group/button-group.styles.js +12 -0
  41. package/lib/components/button-group/button-group.styles.js.map +1 -1
  42. package/lib/components/card/card.d.ts +1 -1
  43. package/lib/components/card/card.d.ts.map +1 -1
  44. package/lib/components/card/card.js +1 -0
  45. package/lib/components/card/card.js.map +1 -1
  46. package/lib/components/card/card.styles.d.ts.map +1 -1
  47. package/lib/components/card/card.styles.js +8 -0
  48. package/lib/components/card/card.styles.js.map +1 -1
  49. package/lib/components/checkbox-field-item/checkbox-field-item.d.ts +6 -0
  50. package/lib/components/checkbox-field-item/checkbox-field-item.d.ts.map +1 -1
  51. package/lib/components/checkbox-field-item/checkbox-field-item.js +6 -0
  52. package/lib/components/checkbox-field-item/checkbox-field-item.js.map +1 -1
  53. package/lib/components/checkbox-field-item/checkbox-field-item.styles.js +1 -1
  54. package/lib/components/checkbox-field-item/checkbox-field-item.styles.js.map +1 -1
  55. package/lib/components/date-picker/date-picker.d.ts +5 -0
  56. package/lib/components/date-picker/date-picker.d.ts.map +1 -1
  57. package/lib/components/date-picker/date-picker.js +12 -10
  58. package/lib/components/date-picker/date-picker.js.map +1 -1
  59. package/lib/components/dropdown/dropdown.d.ts +2 -0
  60. package/lib/components/dropdown/dropdown.d.ts.map +1 -1
  61. package/lib/components/dropdown/dropdown.js +2 -0
  62. package/lib/components/dropdown/dropdown.js.map +1 -1
  63. package/lib/components/dropdown-item/dropdown-item.d.ts +8 -0
  64. package/lib/components/dropdown-item/dropdown-item.d.ts.map +1 -1
  65. package/lib/components/dropdown-item/dropdown-item.js +15 -2
  66. package/lib/components/dropdown-item/dropdown-item.js.map +1 -1
  67. package/lib/components/feature/feature.d.ts +4 -0
  68. package/lib/components/feature/feature.d.ts.map +1 -1
  69. package/lib/components/feature/feature.js +4 -1
  70. package/lib/components/feature/feature.js.map +1 -1
  71. package/lib/components/field/field.d.ts +2 -0
  72. package/lib/components/field/field.d.ts.map +1 -1
  73. package/lib/components/field/field.js +3 -0
  74. package/lib/components/field/field.js.map +1 -1
  75. package/lib/components/global-nav-item/global-nav-item.d.ts +2 -0
  76. package/lib/components/global-nav-item/global-nav-item.d.ts.map +1 -1
  77. package/lib/components/global-nav-item/global-nav-item.js +2 -0
  78. package/lib/components/global-nav-item/global-nav-item.js.map +1 -1
  79. package/lib/components/header/header.d.ts +2 -0
  80. package/lib/components/header/header.d.ts.map +1 -1
  81. package/lib/components/header/header.js +2 -0
  82. package/lib/components/header/header.js.map +1 -1
  83. package/lib/components/hero/hero.d.ts +4 -0
  84. package/lib/components/hero/hero.d.ts.map +1 -1
  85. package/lib/components/hero/hero.js +4 -0
  86. package/lib/components/hero/hero.js.map +1 -1
  87. package/lib/components/hero/hero.styles.d.ts.map +1 -1
  88. package/lib/components/hero/hero.styles.js +13 -0
  89. package/lib/components/hero/hero.styles.js.map +1 -1
  90. package/lib/components/icon/icon.d.ts +3 -0
  91. package/lib/components/icon/icon.d.ts.map +1 -1
  92. package/lib/components/icon/icon.js +7 -5
  93. package/lib/components/icon/icon.js.map +1 -1
  94. package/lib/components/link/link.d.ts +2 -0
  95. package/lib/components/link/link.d.ts.map +1 -1
  96. package/lib/components/link/link.js +2 -0
  97. package/lib/components/link/link.js.map +1 -1
  98. package/lib/components/link-list-item/link-list-item.d.ts +2 -0
  99. package/lib/components/link-list-item/link-list-item.d.ts.map +1 -1
  100. package/lib/components/link-list-item/link-list-item.js +2 -0
  101. package/lib/components/link-list-item/link-list-item.js.map +1 -1
  102. package/lib/components/modal/modal.d.ts +4 -0
  103. package/lib/components/modal/modal.d.ts.map +1 -1
  104. package/lib/components/modal/modal.js +4 -0
  105. package/lib/components/modal/modal.js.map +1 -1
  106. package/lib/components/multi-select/multi-select.d.ts +4 -0
  107. package/lib/components/multi-select/multi-select.d.ts.map +1 -1
  108. package/lib/components/multi-select/multi-select.js +5 -2
  109. package/lib/components/multi-select/multi-select.js.map +1 -1
  110. package/lib/components/page-header/page-header.d.ts +1 -0
  111. package/lib/components/page-header/page-header.d.ts.map +1 -1
  112. package/lib/components/page-header/page-header.js +3 -2
  113. package/lib/components/page-header/page-header.js.map +1 -1
  114. package/lib/components/pagination/pagination.d.ts +7 -0
  115. package/lib/components/pagination/pagination.d.ts.map +1 -1
  116. package/lib/components/pagination/pagination.js +7 -0
  117. package/lib/components/pagination/pagination.js.map +1 -1
  118. package/lib/components/percent-bar/percent-bar.d.ts.map +1 -1
  119. package/lib/components/percent-bar/percent-bar.js +1 -0
  120. package/lib/components/percent-bar/percent-bar.js.map +1 -1
  121. package/lib/components/popover/popover.d.ts +6 -0
  122. package/lib/components/popover/popover.d.ts.map +1 -1
  123. package/lib/components/popover/popover.js +7 -1
  124. package/lib/components/popover/popover.js.map +1 -1
  125. package/lib/components/primary-nav-item/primary-nav-item.d.ts +2 -0
  126. package/lib/components/primary-nav-item/primary-nav-item.d.ts.map +1 -1
  127. package/lib/components/primary-nav-item/primary-nav-item.js +2 -0
  128. package/lib/components/primary-nav-item/primary-nav-item.js.map +1 -1
  129. package/lib/components/remove-tag/remove-tag.d.ts +2 -0
  130. package/lib/components/remove-tag/remove-tag.d.ts.map +1 -1
  131. package/lib/components/remove-tag/remove-tag.js +3 -1
  132. package/lib/components/remove-tag/remove-tag.js.map +1 -1
  133. package/lib/components/section/section.d.ts +1 -0
  134. package/lib/components/section/section.d.ts.map +1 -1
  135. package/lib/components/section/section.js +1 -0
  136. package/lib/components/section/section.js.map +1 -1
  137. package/lib/components/select/select.d.ts +2 -0
  138. package/lib/components/select/select.d.ts.map +1 -1
  139. package/lib/components/select/select.js +5 -1
  140. package/lib/components/select/select.js.map +1 -1
  141. package/lib/components/split-button/split-button.d.ts +3 -0
  142. package/lib/components/split-button/split-button.d.ts.map +1 -1
  143. package/lib/components/split-button/split-button.js.map +1 -1
  144. package/lib/components/tab/tab.d.ts +2 -0
  145. package/lib/components/tab/tab.d.ts.map +1 -1
  146. package/lib/components/tab/tab.js +2 -0
  147. package/lib/components/tab/tab.js.map +1 -1
  148. package/lib/components/table-object/table-object.d.ts +2 -0
  149. package/lib/components/table-object/table-object.d.ts.map +1 -1
  150. package/lib/components/table-object/table-object.js +2 -0
  151. package/lib/components/table-object/table-object.js.map +1 -1
  152. package/lib/components/table-row/table-row.d.ts +1 -0
  153. package/lib/components/table-row/table-row.d.ts.map +1 -1
  154. package/lib/components/table-row/table-row.js +1 -0
  155. package/lib/components/table-row/table-row.js.map +1 -1
  156. package/lib/components/tag/tag.d.ts +5 -0
  157. package/lib/components/tag/tag.d.ts.map +1 -1
  158. package/lib/components/tag/tag.js +2 -0
  159. package/lib/components/tag/tag.js.map +1 -1
  160. package/lib/components/tag-list/tag-list.d.ts +2 -0
  161. package/lib/components/tag-list/tag-list.d.ts.map +1 -1
  162. package/lib/components/tag-list/tag-list.js +2 -0
  163. package/lib/components/tag-list/tag-list.js.map +1 -1
  164. package/lib/components/text-link/text-link.d.ts +1 -0
  165. package/lib/components/text-link/text-link.d.ts.map +1 -1
  166. package/lib/components/text-link/text-link.js +1 -0
  167. package/lib/components/text-link/text-link.js.map +1 -1
  168. package/lib/components/tooltip/tooltip.d.ts +6 -0
  169. package/lib/components/tooltip/tooltip.d.ts.map +1 -1
  170. package/lib/components/tooltip/tooltip.js +3 -0
  171. package/lib/components/tooltip/tooltip.js.map +1 -1
  172. package/lib/design-tokens/brands/claude-terracotta/css/tokens_brand.css +85 -0
  173. package/lib/design-tokens/brands/claude-terracotta/css/tokens_claude-terracotta.css +1 -0
  174. package/lib/design-tokens/brands/claude-terracotta/css/tokens_claude-terracotta.module.d.ts +3 -0
  175. package/lib/design-tokens/brands/claude-terracotta/css/tokens_claude-terracotta.module.d.ts.map +1 -0
  176. package/lib/design-tokens/brands/claude-terracotta/css/tokens_claude-terracotta.module.js +90 -0
  177. package/lib/design-tokens/brands/claude-terracotta/css/tokens_claude-terracotta.module.js.map +1 -0
  178. package/lib/design-tokens/brands/cre8-a2ui/css/tokens_brand.css +1 -0
  179. package/lib/design-tokens/brands/cre8-teal/css/tokens_brand.css +87 -0
  180. package/lib/design-tokens/brands/cre8-teal/css/tokens_cre8-teal.css +1 -0
  181. package/lib/design-tokens/brands/cre8-teal/css/tokens_cre8-teal.module.d.ts +3 -0
  182. package/lib/design-tokens/brands/cre8-teal/css/tokens_cre8-teal.module.d.ts.map +1 -0
  183. package/lib/design-tokens/brands/cre8-teal/css/tokens_cre8-teal.module.js +90 -0
  184. package/lib/design-tokens/brands/cre8-teal/css/tokens_cre8-teal.module.js.map +1 -0
  185. package/lib/scripts/generate-react-manifest.d.ts +20 -0
  186. package/lib/scripts/generate-react-manifest.d.ts.map +1 -0
  187. package/lib/scripts/generate-react-manifest.js +154 -0
  188. package/lib/scripts/generate-react-manifest.js.map +1 -0
  189. package/mcp-manifest.json +203 -38
  190. package/package.json +10 -4
  191. package/react-manifest.json +3594 -0
  192. package/dist/leaf.png +0 -0
  193. package/dist/leafDS.png +0 -0
  194. package/dist/leafLogo.svg +0 -1
@@ -0,0 +1,297 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Build a knowledge graph from the cre8-wc a2ui catalog.json.
4
+
5
+ Outputs:
6
+ catalog-kg.json — queryable node/edge graph
7
+ catalog.dot — GraphViz source
8
+ catalog.svg — rendered SVG
9
+
10
+ Usage:
11
+ cd packages/cre8-wc/a2ui
12
+ python3 build-knowledge-graph.py
13
+ """
14
+
15
+ import json
16
+ import os
17
+ import re
18
+ from pathlib import Path
19
+ import graphviz
20
+
21
+ # ── Config ────────────────────────────────────────────────────────────────────
22
+
23
+ HERE = Path(__file__).parent
24
+ CATALOG_PATH = HERE / "catalog.json"
25
+ KG_OUT = HERE / "catalog-kg.json"
26
+ DOT_OUT = HERE / "catalog" # graphviz adds .dot / .svg extension
27
+
28
+ CATEGORY_COLORS = {
29
+ "Actions": ("#1565C0", "#E3F2FD"), # blue
30
+ "Data": ("#2E7D32", "#E8F5E9"), # green
31
+ "Disclosure": ("#6A1B9A", "#F3E5F5"), # purple
32
+ "Feedback": ("#E65100", "#FFF3E0"), # orange
33
+ "Forms": ("#00838F", "#E0F7FA"), # teal
34
+ "Layout": ("#37474F", "#ECEFF1"), # slate
35
+ "Marketing": ("#AD1457", "#FCE4EC"), # pink
36
+ "Media": ("#4527A0", "#EDE7F6"), # indigo
37
+ "Navigation": ("#558B2F", "#F9FBE7"), # lime
38
+ "Other": ("#546E7A", "#F5F5F5"), # grey
39
+ "Typography": ("#4E342E", "#EFEBE9"), # brown
40
+ }
41
+ DEFAULT_COLOR = ("#546E7A", "#F5F5F5")
42
+
43
+ # ── Load catalog ──────────────────────────────────────────────────────────────
44
+
45
+ with open(CATALOG_PATH) as f:
46
+ catalog = json.load(f)
47
+
48
+ defs = catalog["$defs"]["components"]
49
+
50
+ # ── Build knowledge graph ─────────────────────────────────────────────────────
51
+
52
+ nodes: dict[str, dict] = {}
53
+ edges: list[dict] = []
54
+
55
+ def short(text: str, n: int = 90) -> str:
56
+ text = re.sub(r"\s+", " ", text.strip())
57
+ return text[:n] + "…" if len(text) > n else text
58
+
59
+ # Category nodes
60
+ all_cats: set[str] = set()
61
+ for comp_def in defs.values():
62
+ all_cats.add(comp_def.get("x-category", "Other"))
63
+
64
+ for cat in sorted(all_cats):
65
+ nodes[f"cat:{cat}"] = {"id": f"cat:{cat}", "type": "category", "label": cat}
66
+
67
+ # Component nodes
68
+ for name, comp_def in defs.items():
69
+ cat = comp_def.get("x-category", "Other")
70
+ desc = short(comp_def.get("description", ""), 120)
71
+ props_raw = comp_def.get("properties", {}).get("props", {}).get("properties", {})
72
+ slots_raw = comp_def.get("properties", {}).get("slots", {}).get("properties", {})
73
+ has_children = "children" in comp_def.get("properties", {})
74
+
75
+ props: dict[str, dict] = {}
76
+ for pname, pdef in props_raw.items():
77
+ props[pname] = {
78
+ "type": pdef.get("type", "any"),
79
+ "enum": pdef.get("enum"),
80
+ "default": pdef.get("default"),
81
+ "description": short(pdef.get("description", ""), 80),
82
+ "x-kind": pdef.get("x-kind", "attribute"),
83
+ }
84
+
85
+ slot_names = list(slots_raw.keys())
86
+
87
+ nodes[name] = {
88
+ "id": name,
89
+ "type": "component",
90
+ "category": cat,
91
+ "description": desc,
92
+ "props": props,
93
+ "slots": slot_names,
94
+ "accepts_children": has_children,
95
+ }
96
+
97
+ edges.append({"from": name, "to": f"cat:{cat}", "rel": "BELONGS_TO"})
98
+
99
+ for slot_name, slot_def in slots_raw.items():
100
+ sid = f"slot:{name}:{slot_name}"
101
+ nodes[sid] = {
102
+ "id": sid,
103
+ "type": "slot",
104
+ "name": slot_name,
105
+ "component": name,
106
+ "description": short(slot_def.get("description", ""), 80),
107
+ }
108
+ edges.append({"from": name, "to": sid, "rel": "HAS_SLOT"})
109
+
110
+ for pname, pdef in props_raw.items():
111
+ if pdef.get("enum"):
112
+ pid = f"prop:{name}:{pname}"
113
+ nodes[pid] = {
114
+ "id": pid,
115
+ "type": "enum_prop",
116
+ "name": pname,
117
+ "component": name,
118
+ "enum": pdef["enum"],
119
+ "description": short(pdef.get("description", ""), 80),
120
+ }
121
+ edges.append({"from": name, "to": pid, "rel": "HAS_ENUM_PROP"})
122
+
123
+ # ── Write catalog-kg.json ─────────────────────────────────────────────────────
124
+
125
+ kg = {
126
+ "meta": {
127
+ "generated_from": "catalog.json",
128
+ "catalog_id": catalog.get("x-a2ui", {}).get("catalogId"),
129
+ "library": catalog.get("x-a2ui", {}).get("library"),
130
+ "library_version": catalog.get("x-a2ui", {}).get("libraryVersion"),
131
+ "total_nodes": len(nodes),
132
+ "total_edges": len(edges),
133
+ },
134
+ "nodes": list(nodes.values()),
135
+ "edges": edges,
136
+ }
137
+
138
+ with open(KG_OUT, "w") as f:
139
+ json.dump(kg, f, indent=2)
140
+
141
+ print(f"[kg] {KG_OUT} — {len(nodes)} nodes, {len(edges)} edges")
142
+
143
+ # ── GraphViz visualization ────────────────────────────────────────────────────
144
+
145
+ def html_escape(s: str) -> str:
146
+ return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")
147
+
148
+ def prop_row(pname: str, pdef: dict) -> str:
149
+ raw_type = pdef.get("type", "any")
150
+ typ = " | ".join(raw_type) if isinstance(raw_type, list) else str(raw_type)
151
+ enums = pdef.get("enum")
152
+ if enums:
153
+ val = " | ".join(html_escape(str(e)) for e in enums[:5])
154
+ if len(enums) > 5:
155
+ val += f" (+{len(enums) - 5})"
156
+ else:
157
+ val = html_escape(typ)
158
+ return (
159
+ f'<TR><TD ALIGN="LEFT" BGCOLOR="#FAFAFA"><FONT POINT-SIZE="9">'
160
+ f' {html_escape(pname)}</FONT></TD>'
161
+ f'<TD ALIGN="LEFT" BGCOLOR="#FAFAFA"><FONT POINT-SIZE="8" COLOR="#666666">'
162
+ f'{val}</FONT></TD></TR>'
163
+ )
164
+
165
+ def component_label(name: str, node: dict) -> str:
166
+ cat = node["category"]
167
+ fg, bg = CATEGORY_COLORS.get(cat, DEFAULT_COLOR)
168
+ props = node["props"]
169
+ slots = node["slots"]
170
+ has_ch = node["accepts_children"]
171
+
172
+ # Header row
173
+ badge_parts = []
174
+ if has_ch:
175
+ badge_parts.append("children")
176
+ for s in slots:
177
+ badge_parts.append(f"[{s}]")
178
+ badge = " " + " · ".join(badge_parts) if badge_parts else ""
179
+
180
+ badge_html = (
181
+ f'<FONT POINT-SIZE="8" COLOR="#888888">{html_escape(badge)}</FONT>'
182
+ if badge else ""
183
+ )
184
+ rows = [
185
+ f'<TR><TD COLSPAN="2" BGCOLOR="{bg}" ALIGN="LEFT">'
186
+ f'<B><FONT COLOR="{fg}" POINT-SIZE="11">{html_escape(name)}</FONT></B>'
187
+ f'{badge_html}'
188
+ f'</TD></TR>'
189
+ ]
190
+
191
+ # Props (up to 6 most interesting)
192
+ shown = list(props.items())[:6]
193
+ for pname, pdef in shown:
194
+ rows.append(prop_row(pname, pdef))
195
+ if len(props) > 6:
196
+ rows.append(
197
+ f'<TR><TD COLSPAN="2" ALIGN="LEFT" BGCOLOR="#F5F5F5">'
198
+ f'<FONT POINT-SIZE="8" COLOR="#999999"> +{len(props) - 6} more props</FONT>'
199
+ f'</TD></TR>'
200
+ )
201
+
202
+ inner = "".join(rows)
203
+ return f'<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="3">{inner}</TABLE>>'
204
+
205
+
206
+ # Build the graph
207
+ g = graphviz.Digraph(
208
+ "cre8-wc",
209
+ comment="cre8 Web Components Knowledge Graph",
210
+ engine="fdp", # force-directed — handles 85 nodes well
211
+ graph_attr={
212
+ "fontname": "Helvetica",
213
+ "fontsize": "11",
214
+ "rankdir": "LR",
215
+ "splines": "ortho",
216
+ "nodesep": "0.5",
217
+ "ranksep": "1.5",
218
+ "overlap": "false",
219
+ "bgcolor": "#FFFFFF",
220
+ "label": "cre8-wc Component Knowledge Graph",
221
+ "labelloc": "t",
222
+ "labelfontsize": "16",
223
+ },
224
+ node_attr={"fontname": "Helvetica", "shape": "none", "margin": "0"},
225
+ edge_attr={"fontname": "Helvetica", "fontsize": "9"},
226
+ )
227
+
228
+ # One subgraph cluster per category
229
+ cat_comps: dict[str, list[str]] = {}
230
+ for name, node in nodes.items():
231
+ if node["type"] == "component":
232
+ cat = node["category"]
233
+ cat_comps.setdefault(cat, []).append(name)
234
+
235
+ for cat in sorted(cat_comps):
236
+ fg, bg = CATEGORY_COLORS.get(cat, DEFAULT_COLOR)
237
+ with g.subgraph(name=f"cluster_{cat.replace(' ', '_')}") as c:
238
+ c.attr(
239
+ label=f" {cat} ",
240
+ style="filled,rounded",
241
+ fillcolor=bg,
242
+ color=fg,
243
+ penwidth="2",
244
+ fontcolor=fg,
245
+ fontsize="13",
246
+ fontname="Helvetica-Bold",
247
+ )
248
+ for comp_name in sorted(cat_comps[cat]):
249
+ node = nodes[comp_name]
250
+ c.node(comp_name, label=component_label(comp_name, node))
251
+
252
+ # Slot edges (only for named slots — keeps graph readable)
253
+ for edge in edges:
254
+ if edge["rel"] == "HAS_SLOT":
255
+ slot_node = nodes[edge["to"]]
256
+ g.edge(
257
+ edge["from"],
258
+ edge["to"],
259
+ label=slot_node["name"],
260
+ style="dashed",
261
+ color="#9C27B0",
262
+ fontcolor="#9C27B0",
263
+ arrowhead="open",
264
+ )
265
+ # Add the slot node itself
266
+ g.node(
267
+ edge["to"],
268
+ label=f'<<FONT POINT-SIZE="9" COLOR="#9C27B0">slot: {html_escape(slot_node["name"])}</FONT>>',
269
+ shape="ellipse",
270
+ style="filled,dashed",
271
+ fillcolor="#F8E8FF",
272
+ color="#9C27B0",
273
+ margin="0.1,0.05",
274
+ )
275
+
276
+ # Render
277
+ g.render(str(DOT_OUT), format="svg", cleanup=False)
278
+ print(f"[dot] {DOT_OUT}.dot")
279
+ print(f"[svg] {DOT_OUT}.svg")
280
+
281
+ # ── Query helpers printed to stdout ──────────────────────────────────────────
282
+ print("\n── Knowledge graph summary ──────────────────────────────────────")
283
+ comp_nodes = [n for n in nodes.values() if n["type"] == "component"]
284
+ print(f" Components : {len(comp_nodes)}")
285
+ for cat in sorted(cat_comps):
286
+ print(f" {cat:15s} {len(cat_comps[cat])} components")
287
+ enum_props = [n for n in nodes.values() if n["type"] == "enum_prop"]
288
+ print(f" Enum props : {len(enum_props)}")
289
+ slot_nodes = [n for n in nodes.values() if n["type"] == "slot"]
290
+ print(f" Named slots: {len(slot_nodes)}")
291
+ print(f" Total edges: {len(edges)}")
292
+ print()
293
+ print("Query with: python3 -c \"")
294
+ print(" import json; kg=json.load(open('a2ui/catalog-kg.json'))")
295
+ print(" comps=[n for n in kg['nodes'] if n['type']=='component']")
296
+ print(" print([c['id'] for c in comps if c['category']=='Forms'])")
297
+ print("\"")