@adia-ai/adia-ui-factory 0.2.4 → 0.8.1

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 (145) hide show
  1. package/.claude-plugin/plugin.json +6 -8
  2. package/.mcp.json +1 -1
  3. package/CHANGELOG.md +40 -67
  4. package/README.md +32 -53
  5. package/agents/app-architect.md +28 -0
  6. package/agents/routing-corpus.json +202 -0
  7. package/agents/screen-composer.md +27 -0
  8. package/bin/adia-info +235 -0
  9. package/bin/adia-lint +17 -2
  10. package/commands/adia-genui.md +5 -8
  11. package/commands/adia-migrate.md +5 -6
  12. package/commands/adia-orient.md +5 -10
  13. package/commands/adia-scaffold.md +7 -13
  14. package/commands/adia-verify.md +5 -6
  15. package/package.json +5 -6
  16. package/references/a2ui-mcp-tools.md +11 -8
  17. package/references/authoring-components.md +25 -1
  18. package/references/component-model.md +52 -4
  19. package/references/contracts/a2ui-mcp-surface.md +35 -0
  20. package/references/contracts/migration-guide-format.md +34 -0
  21. package/references/data-and-hydration.md +8 -5
  22. package/references/genui-a2ui.md +12 -6
  23. package/references/llm.md +22 -39
  24. package/references/migration.md +110 -36
  25. package/references/project-shapes.md +11 -13
  26. package/references/shell-admin.md +5 -2
  27. package/references/shell-chat.md +5 -3
  28. package/references/shell-editor.md +1 -1
  29. package/references/shell-embed.md +40 -26
  30. package/references/shell-simple.md +5 -2
  31. package/references/spa-architecture.md +28 -73
  32. package/references/ssr-integration.md +1 -1
  33. package/references/verification.md +8 -12
  34. package/skills/adia-compose/SKILL.md +121 -0
  35. package/skills/{adia-ui-kit → adia-compose}/assets/figma-make/guidelines/Guidelines.md +3 -3
  36. package/skills/{adia-ui-kit → adia-compose}/assets/figma-make/guidelines/components.md +1 -1
  37. package/skills/{adia-ui-kit → adia-compose}/assets/figma-make/guidelines/styles.md +1 -1
  38. package/skills/{adia-ui-kit → adia-compose}/assets/figma-make/guidelines/tokens.md +3 -3
  39. package/skills/{adia-ui-kit → adia-compose}/assets/templates/FEEDBACK.template.md +7 -7
  40. package/skills/{adia-ui-kit → adia-compose}/assets/templates/RESPONSE.template.md +6 -7
  41. package/skills/adia-compose/references/composition-traps.md +58 -0
  42. package/skills/adia-compose/references/feedback-discipline.md +20 -0
  43. package/skills/adia-compose/references/spec-to-ui-reasoning.md +158 -0
  44. package/skills/adia-data/SKILL.md +78 -0
  45. package/skills/adia-genui/SKILL.md +83 -0
  46. package/skills/adia-host/SKILL.md +119 -0
  47. package/skills/adia-llm/SKILL.md +36 -0
  48. package/skills/adia-migrate/SKILL.md +95 -0
  49. package/skills/adia-orient/SKILL.md +135 -0
  50. package/skills/adia-project/SKILL.md +83 -0
  51. package/skills/adia-shells/SKILL.md +74 -0
  52. package/skills/adia-verify/SKILL.md +43 -0
  53. package/skills/adia-verify/references/verification.md +42 -0
  54. package/commands/adia-compose.md +0 -10
  55. package/commands/adia-wire.md +0 -13
  56. package/skills/adia-ui-compose/SKILL.md +0 -57
  57. package/skills/adia-ui-data/SKILL.md +0 -62
  58. package/skills/adia-ui-factory/SKILL.md +0 -113
  59. package/skills/adia-ui-genui/SKILL.md +0 -74
  60. package/skills/adia-ui-kit/CHANGELOG.md +0 -41
  61. package/skills/adia-ui-kit/SKILL.md +0 -442
  62. package/skills/adia-ui-kit/assets/team-scaffold/archive-README.md +0 -30
  63. package/skills/adia-ui-kit/assets/team-scaffold/drafts-README.md +0 -57
  64. package/skills/adia-ui-kit/assets/team-scaffold/inbox-README.md +0 -76
  65. package/skills/adia-ui-kit/assets/team-scaffold/outbox-README.md +0 -46
  66. package/skills/adia-ui-kit/assets/team-scaffold/team-root.README.md +0 -51
  67. package/skills/adia-ui-kit/assets/team-scaffold/tickets-README.md +0 -208
  68. package/skills/adia-ui-kit/assets/team-scaffold/tickets-REGISTRY.md +0 -81
  69. package/skills/adia-ui-kit/assets/templates/AGENTS.template.md +0 -128
  70. package/skills/adia-ui-kit/assets/templates/CLAUDE.template.md +0 -1
  71. package/skills/adia-ui-kit/assets/templates/figma-make-guidelines.md +0 -90
  72. package/skills/adia-ui-kit/evals/adversarial-content-trust.json +0 -83
  73. package/skills/adia-ui-kit/evals/evals.json +0 -55
  74. package/skills/adia-ui-kit/evals/routing-corpus.json +0 -121
  75. package/skills/adia-ui-kit/evals/teach-routing-cases.json +0 -92
  76. package/skills/adia-ui-kit/references/capabilities.md +0 -245
  77. package/skills/adia-ui-kit/references/catalog-lookup.md +0 -186
  78. package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v1.md +0 -8
  79. package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v2.md +0 -3585
  80. package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v3.md +0 -1899
  81. package/skills/adia-ui-kit/references/common-gotchas-consumer.md +0 -522
  82. package/skills/adia-ui-kit/references/component-literacy.md +0 -291
  83. package/skills/adia-ui-kit/references/composition.md +0 -245
  84. package/skills/adia-ui-kit/references/correction-loop.md +0 -462
  85. package/skills/adia-ui-kit/references/css-policy.md +0 -343
  86. package/skills/adia-ui-kit/references/decision-model.md +0 -158
  87. package/skills/adia-ui-kit/references/diagnostic-output.md +0 -57
  88. package/skills/adia-ui-kit/references/docs-catalog.md +0 -239
  89. package/skills/adia-ui-kit/references/exclusions.md +0 -225
  90. package/skills/adia-ui-kit/references/feedback-authoring.md +0 -466
  91. package/skills/adia-ui-kit/references/figma-make.md +0 -244
  92. package/skills/adia-ui-kit/references/flows-auth.md +0 -307
  93. package/skills/adia-ui-kit/references/flows-content.md +0 -206
  94. package/skills/adia-ui-kit/references/flows-data.md +0 -348
  95. package/skills/adia-ui-kit/references/flows-interaction.md +0 -181
  96. package/skills/adia-ui-kit/references/flows-shells.md +0 -336
  97. package/skills/adia-ui-kit/references/gap-detection.md +0 -266
  98. package/skills/adia-ui-kit/references/harness.md +0 -284
  99. package/skills/adia-ui-kit/references/loading-protocol.md +0 -406
  100. package/skills/adia-ui-kit/references/local-link.md +0 -198
  101. package/skills/adia-ui-kit/references/mcp.md +0 -401
  102. package/skills/adia-ui-kit/references/migration.md +0 -116
  103. package/skills/adia-ui-kit/references/mission.md +0 -194
  104. package/skills/adia-ui-kit/references/mode-routing.md +0 -298
  105. package/skills/adia-ui-kit/references/patterns-agent.md +0 -223
  106. package/skills/adia-ui-kit/references/patterns-data.md +0 -1065
  107. package/skills/adia-ui-kit/references/patterns-forms.md +0 -223
  108. package/skills/adia-ui-kit/references/patterns-layout.md +0 -326
  109. package/skills/adia-ui-kit/references/patterns-recipes.md +0 -1416
  110. package/skills/adia-ui-kit/references/patterns-shells.md +0 -1097
  111. package/skills/adia-ui-kit/references/patterns-traits.md +0 -259
  112. package/skills/adia-ui-kit/references/patterns-wiring.md +0 -339
  113. package/skills/adia-ui-kit/references/pev-rationale.md +0 -137
  114. package/skills/adia-ui-kit/references/reasoning-gate.md +0 -146
  115. package/skills/adia-ui-kit/references/reasoning-methodologies.md +0 -112
  116. package/skills/adia-ui-kit/references/recon.md +0 -192
  117. package/skills/adia-ui-kit/references/rendering-model.md +0 -431
  118. package/skills/adia-ui-kit/references/response-authoring.md +0 -398
  119. package/skills/adia-ui-kit/references/session-log-schema.md +0 -203
  120. package/skills/adia-ui-kit/references/session-log-template.md +0 -118
  121. package/skills/adia-ui-kit/references/setup.md +0 -652
  122. package/skills/adia-ui-kit/references/skill-hygiene.md +0 -135
  123. package/skills/adia-ui-kit/references/spec-to-ui-reasoning.md +0 -394
  124. package/skills/adia-ui-kit/references/state.md +0 -229
  125. package/skills/adia-ui-kit/references/teach-protocol.md +0 -421
  126. package/skills/adia-ui-kit/references/theme.md +0 -142
  127. package/skills/adia-ui-kit/references/version-check.md +0 -69
  128. package/skills/adia-ui-kit/scripts/audit-decision-trace.mjs +0 -198
  129. package/skills/adia-ui-kit/scripts/audit-kit-roster.mjs +0 -366
  130. package/skills/adia-ui-kit/scripts/changelog-archive.mjs +0 -126
  131. package/skills/adia-ui-kit/scripts/check-adia-versions.py +0 -404
  132. package/skills/adia-ui-kit/scripts/run-skill-evals.mjs +0 -358
  133. package/skills/adia-ui-kit/scripts/validate-session-log.mjs +0 -242
  134. package/skills/adia-ui-kit/skill.json +0 -102
  135. package/skills/adia-ui-llm/SKILL.md +0 -51
  136. package/skills/adia-ui-migrate/SKILL.md +0 -64
  137. package/skills/adia-ui-migration/CHANGELOG.md +0 -47
  138. package/skills/adia-ui-migration/SKILL.md +0 -381
  139. package/skills/adia-ui-migration/skill.json +0 -29
  140. package/skills/adia-ui-project/SKILL.md +0 -77
  141. package/skills/adia-ui-shells/SKILL.md +0 -62
  142. package/skills/adia-ui-spa/SKILL.md +0 -52
  143. package/skills/adia-ui-ssr/SKILL.md +0 -52
  144. package/skills/adia-ui-verify/SKILL.md +0 -44
  145. /package/skills/{adia-ui-kit → adia-compose}/assets/templates/FEEDBACK-OMISSIONS-DEFENSE.template.md +0 -0
package/bin/adia-info ADDED
@@ -0,0 +1,235 @@
1
+ #!/usr/bin/env python3
2
+ """adia-info — one-shot project-context probe for adia-ui consumer repos.
3
+
4
+ Prints a compact JSON snapshot of the facts the factory skills otherwise
5
+ re-discover by hand every session: which @adia-ai/* packages the project
6
+ declares (and which version is actually installed), the rendering-mode and
7
+ framework signals adia-orient's classifiers cite, which shells the source
8
+ uses, theming setup, MCP wiring, and whether the cwd is actually the
9
+ framework monorepo itself (a misroute signal — that work belongs to the
10
+ adia-ui-forge plugin).
11
+
12
+ Designed for dynamic context injection from a SKILL.md body:
13
+
14
+ !`python3 "${CLAUDE_PLUGIN_ROOT}/bin/adia-info"`
15
+
16
+ so every field carries the SIGNAL it was derived from (file path or dep
17
+ name) — a skill consuming this output can cite signals per adia-orient's
18
+ evidence gate instead of asserting axes bare.
19
+
20
+ Contract: NEVER exits non-zero, never prints to stderr on the happy path —
21
+ a context-injection command that fails or noises breaks the skill it feeds.
22
+ Any probe error degrades that field to null with the error noted in
23
+ `probeErrors`. Bounded: scans at most MAX_SCAN_FILES source files, skipping
24
+ node_modules/dist/.git.
25
+
26
+ Usage:
27
+ adia-info [dir] # probe dir (default: cwd); print JSON to stdout
28
+ adia-info selftest # run built-in fixtures; exit 0 iff all pass
29
+ Stdlib only (Python 3.8+).
30
+ """
31
+ import json
32
+ import os
33
+ import re
34
+ import sys
35
+
36
+ MAX_SCAN_FILES = 400
37
+ SOURCE_EXTS = (".html", ".js", ".mjs", ".ts", ".jsx", ".tsx", ".css")
38
+ SKIP_DIRS = {"node_modules", "dist", ".git", ".next", ".nuxt", ".svelte-kit", "build", "coverage"}
39
+
40
+ SSR_FRAMEWORKS = { # dep name -> framework label
41
+ "next": "next", "nuxt": "nuxt", "@sveltejs/kit": "sveltekit", "astro": "astro",
42
+ }
43
+ SHELL_TAGS = ("admin-shell", "chat-shell", "editor-shell", "simple-shell", "embed-shell")
44
+
45
+
46
+ def _read_json(path):
47
+ try:
48
+ with open(path, encoding="utf-8") as f:
49
+ return json.load(f)
50
+ except Exception:
51
+ return None
52
+
53
+
54
+ def _iter_source_files(root):
55
+ count = 0
56
+ for dirpath, dirnames, filenames in os.walk(root):
57
+ dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS and not d.startswith(".")]
58
+ for name in filenames:
59
+ if name.endswith(SOURCE_EXTS):
60
+ yield os.path.join(dirpath, name)
61
+ count += 1
62
+ if count >= MAX_SCAN_FILES:
63
+ return
64
+
65
+
66
+ def probe(root):
67
+ info = {"probeRoot": os.path.abspath(root), "probeErrors": []}
68
+
69
+ # ── package.json: adia deps, framework, package manager ──
70
+ pkg = _read_json(os.path.join(root, "package.json")) or {}
71
+ deps = {}
72
+ for key in ("dependencies", "devDependencies"):
73
+ deps.update(pkg.get(key) or {})
74
+ info["adiaPackages"] = {k: v for k, v in sorted(deps.items()) if k.startswith("@adia-ai/")}
75
+
76
+ installed = _read_json(os.path.join(root, "node_modules", "@adia-ai", "web-components", "package.json"))
77
+ info["installedVersion"] = installed.get("version") if installed else None
78
+
79
+ framework = next((label for dep, label in SSR_FRAMEWORKS.items() if dep in deps), None)
80
+ if framework is None and "vite" in deps:
81
+ framework = "vite"
82
+ info["framework"] = framework
83
+
84
+ pm = pkg.get("packageManager")
85
+ if pm:
86
+ info["packageManager"] = {"value": pm.split("@")[0], "signal": "package.json packageManager field"}
87
+ else:
88
+ for lockfile, name in (("pnpm-lock.yaml", "pnpm"), ("bun.lockb", "bun"), ("bun.lock", "bun"),
89
+ ("yarn.lock", "yarn"), ("package-lock.json", "npm")):
90
+ if os.path.exists(os.path.join(root, lockfile)):
91
+ info["packageManager"] = {"value": name, "signal": lockfile}
92
+ break
93
+ else:
94
+ info["packageManager"] = None
95
+
96
+ # ── framework-monorepo misroute signal ──
97
+ info["isFrameworkMonorepo"] = (
98
+ os.path.isdir(os.path.join(root, "packages", "web-components", "components"))
99
+ and os.path.isdir(os.path.join(root, "packages", "a2ui"))
100
+ )
101
+
102
+ # ── rendering mode (adia-orient classifier 1, same signals) ──
103
+ if framework in SSR_FRAMEWORKS.values():
104
+ route_dirs = [d for d in ("app", "pages", "src/routes", "src/pages") if os.path.isdir(os.path.join(root, d))]
105
+ info["renderingMode"] = {"value": "ssr", "signal": f"{framework} in package.json" + (f" + {route_dirs[0]}/" if route_dirs else "")}
106
+ elif info["adiaPackages"] or os.path.exists(os.path.join(root, "index.html")):
107
+ info["renderingMode"] = {"value": "spa", "signal": "no SSR framework dep" + ("; index.html present" if os.path.exists(os.path.join(root, "index.html")) else "")}
108
+ else:
109
+ info["renderingMode"] = {"value": "unknown", "signal": "no package.json deps or index.html found"}
110
+
111
+ # ── source scan: shells, registration, theming, custom tags ──
112
+ shells, register_files, theme = set(), [], {"themesCss": False, "dataScheme": False, "namedTheme": False}
113
+ scanned = 0
114
+ try:
115
+ for path in _iter_source_files(root):
116
+ scanned += 1
117
+ try:
118
+ with open(path, encoding="utf-8", errors="ignore") as f:
119
+ text = f.read()
120
+ except Exception:
121
+ continue
122
+ for tag in SHELL_TAGS:
123
+ if "<" + tag in text or "adia-" + tag in text:
124
+ shells.add(tag)
125
+ if "@adia-ai/web-components" in text or "@adia-ai/web-modules" in text:
126
+ register_files.append(os.path.relpath(path, root))
127
+ if "themes.css" in text:
128
+ theme["themesCss"] = True
129
+ if "data-scheme" in text:
130
+ theme["dataScheme"] = True
131
+ if re.search(r'\btheme="[a-z]', text):
132
+ theme["namedTheme"] = True
133
+ except Exception as e: # pragma: no cover — never let a scan error kill the probe
134
+ info["probeErrors"].append(f"source scan: {e}")
135
+ info["shellsUsed"] = sorted(shells)
136
+ info["registrationFiles"] = sorted(register_files)[:10]
137
+ info["theme"] = theme
138
+ info["scannedFiles"] = scanned
139
+
140
+ # ── MCP wiring ──
141
+ mcp = _read_json(os.path.join(root, ".mcp.json"))
142
+ servers = (mcp or {}).get("mcpServers") or {}
143
+ a2ui = next((v for k, v in servers.items() if "a2ui" in k), None)
144
+ if a2ui:
145
+ args = " ".join(a2ui.get("args") or [])
146
+ pin = re.search(r"@adia-ai/a2ui-mcp@([\w.\-]+)", args)
147
+ info["a2uiMcp"] = {"configured": True, "pin": pin.group(1) if pin else None}
148
+ else:
149
+ info["a2uiMcp"] = {"configured": False, "pin": None}
150
+
151
+ info["typescript"] = os.path.exists(os.path.join(root, "tsconfig.json"))
152
+ return info
153
+
154
+
155
+ def _selftest():
156
+ import shutil
157
+ import tempfile
158
+
159
+ failures = []
160
+
161
+ def check(name, cond):
162
+ if not cond:
163
+ failures.append(name)
164
+
165
+ # Fixture 1: SPA consumer with adia deps, shells, theme, MCP pin.
166
+ tmp = tempfile.mkdtemp(prefix="adia-info-spa-")
167
+ try:
168
+ with open(os.path.join(tmp, "package.json"), "w") as f:
169
+ json.dump({"dependencies": {"@adia-ai/web-components": "^0.8.0", "vite": "^5.0.0"}}, f)
170
+ with open(os.path.join(tmp, "package-lock.json"), "w") as f:
171
+ f.write("{}")
172
+ with open(os.path.join(tmp, "index.html"), "w") as f:
173
+ f.write('<html data-scheme="dark" theme="ocean"><link href="themes.css">'
174
+ '<script type="module">import "@adia-ai/web-components";</script>'
175
+ "<admin-shell></admin-shell></html>")
176
+ with open(os.path.join(tmp, ".mcp.json"), "w") as f:
177
+ json.dump({"mcpServers": {"a2ui": {"args": ["-y", "@adia-ai/a2ui-mcp@0.8.0"]}}}, f)
178
+ r = probe(tmp)
179
+ check("spa: mode", r["renderingMode"]["value"] == "spa")
180
+ check("spa: adia dep", "@adia-ai/web-components" in r["adiaPackages"])
181
+ check("spa: framework vite", r["framework"] == "vite")
182
+ check("spa: pm npm via lockfile", r["packageManager"] == {"value": "npm", "signal": "package-lock.json"})
183
+ check("spa: shell found", r["shellsUsed"] == ["admin-shell"])
184
+ check("spa: registration file", r["registrationFiles"] == ["index.html"])
185
+ check("spa: theme trio", r["theme"] == {"themesCss": True, "dataScheme": True, "namedTheme": True})
186
+ check("spa: mcp pin", r["a2uiMcp"] == {"configured": True, "pin": "0.8.0"})
187
+ check("spa: not monorepo", r["isFrameworkMonorepo"] is False)
188
+ finally:
189
+ shutil.rmtree(tmp, ignore_errors=True)
190
+
191
+ # Fixture 2: SSR (next) consumer.
192
+ tmp = tempfile.mkdtemp(prefix="adia-info-ssr-")
193
+ try:
194
+ with open(os.path.join(tmp, "package.json"), "w") as f:
195
+ json.dump({"dependencies": {"next": "15.0.0", "@adia-ai/web-components": "^0.8.0"},
196
+ "packageManager": "pnpm@9.0.0"}, f)
197
+ os.makedirs(os.path.join(tmp, "app"))
198
+ r = probe(tmp)
199
+ check("ssr: mode", r["renderingMode"]["value"] == "ssr")
200
+ check("ssr: signal cites next", "next" in r["renderingMode"]["signal"])
201
+ check("ssr: pm field wins", r["packageManager"]["value"] == "pnpm")
202
+ check("ssr: mcp unconfigured", r["a2uiMcp"]["configured"] is False)
203
+ finally:
204
+ shutil.rmtree(tmp, ignore_errors=True)
205
+
206
+ # Fixture 3: empty dir never crashes, mode unknown.
207
+ tmp = tempfile.mkdtemp(prefix="adia-info-empty-")
208
+ try:
209
+ r = probe(tmp)
210
+ check("empty: mode unknown", r["renderingMode"]["value"] == "unknown")
211
+ check("empty: no errors", r["probeErrors"] == [])
212
+ json.dumps(r) # must be serializable
213
+ finally:
214
+ shutil.rmtree(tmp, ignore_errors=True)
215
+
216
+ if failures:
217
+ print("selftest FAIL: " + ", ".join(failures), file=sys.stderr)
218
+ return 1
219
+ print("selftest OK — 3 fixtures")
220
+ return 0
221
+
222
+
223
+ def main(argv):
224
+ if len(argv) > 1 and argv[1] == "selftest":
225
+ return _selftest()
226
+ root = argv[1] if len(argv) > 1 else "."
227
+ try:
228
+ print(json.dumps(probe(root), indent=1))
229
+ except Exception as e: # ultimate fail-soft: emit the error as JSON, exit 0
230
+ print(json.dumps({"probeErrors": [str(e)]}))
231
+ return 0
232
+
233
+
234
+ if __name__ == "__main__":
235
+ sys.exit(main(sys.argv))
package/bin/adia-lint CHANGED
@@ -186,8 +186,19 @@ def _hook():
186
186
  return 0
187
187
  findings = lint_text(text, path)
188
188
  if findings:
189
- print(_render(path, findings))
190
- print(" (advisory adia-ui authoring smells; the skills + a2ui MCP own the judgment)")
189
+ # PostToolUse exit-0 stdout is NOT fed to the model — only structured
190
+ # JSON reaches it. additionalContext delivers the advisory repair loop
191
+ # while preserving the never-block invariant.
192
+ context = (
193
+ _render(path, findings)
194
+ + "\n (adia-lint advisory — adia-ui authoring smells; the skills + a2ui MCP own the judgment)"
195
+ )
196
+ print(json.dumps({
197
+ "hookSpecificOutput": {
198
+ "hookEventName": "PostToolUse",
199
+ "additionalContext": context,
200
+ }
201
+ }))
191
202
  return 0 # NEVER block
192
203
 
193
204
 
@@ -235,11 +246,15 @@ def _selftest():
235
246
  "content": "@scope (x) {\n :scope { color:#f00; }\n}"}}))
236
247
  sys.stdout = io.StringIO()
237
248
  rc = _hook()
249
+ hook_out = sys.stdout.getvalue()
238
250
  finally:
239
251
  sys.stdin, sys.stdout = saved_in, saved_out
240
252
  if rc != 0:
241
253
  ok = False
242
254
  print("selftest: --hook did not exit 0 on smelly input", file=sys.stderr)
255
+ if rc == 0 and hook_out and '"hookSpecificOutput"' not in hook_out:
256
+ ok = False
257
+ print("selftest: --hook stdout is not structured hookSpecificOutput JSON", file=sys.stderr)
243
258
  print("selftest: PASS" if ok else "selftest: FAIL")
244
259
  return 0 if ok else 1
245
260
 
@@ -1,12 +1,9 @@
1
1
  ---
2
- description: Author a generative-UI experience on adia-ui the a2ui runtime + generate_ui + corpus (core or your own).
3
- argument-hint: "[what to generate]"
2
+ description: Author a generative-UI experience wire the a2ui runtime, generate_ui/refine_ui via the MCP, choose core vs custom corpus.
3
+ argument-hint: "[experience or surface]"
4
4
  ---
5
5
 
6
- Author a generative-UI experience. **$ARGUMENTS**
6
+ Build a gen-UI experience. **$ARGUMENTS**
7
7
 
8
- **Name the design intent first [soft-gate]:** the design intent (the `BRIEF` — what this UI is reaching for, and what grounds the corpus retrieval) must be **at least lightly named**, one sentence is enough and it will evolve; absent, set a provisional, revisable pull and proceed. This is a **soft gate**, cleared by _naming_ a direction, not by stopping.
9
-
10
- Hand off to **`adia-ui-genui`** and run the loop: classify + ground (corpus) → `generate_ui` → **validate** (`validate_schema` + `check_anti_patterns`) _before_ render → mount `<a2ui-root>` (register resolvers first) → `refine_ui`. Generated A2UI and corpus output are untrusted data.
11
-
12
- A plain chat/LLM feature is `adia-ui-llm`, not this. The skill owns the depth.
8
+ Invoke **`adia-genui`** and run its generate validate render iterate
9
+ loop; generated A2UI is validated as data before it is serialized.
@@ -1,10 +1,9 @@
1
1
  ---
2
- description: Migrate an adia-ui app — upgrade across @adia-ai versions, port to adia-ui, or change rendering mode.
3
- argument-hint: "[upgrade|port|mode] [target]"
2
+ description: Migrate consumer code across @adia-ai versions (or port a foreign codebase to adia-ui) — grep audit, mechanical sweeps, verify gates.
3
+ argument-hint: "[target version]"
4
4
  ---
5
5
 
6
- Migrate an adia-ui app. **$ARGUMENTS**
6
+ Migrate this codebase. **$ARGUMENTS**
7
7
 
8
- Hand off to **`adia-ui-migrate`** and run its 5-step discipline: read the migration guide audit call sites (`git grep`) → apply mechanical sweeps (flag judgment items, don't auto-apply) → run the verify gates (`adia-lint` + render + the leftover-drift grep) → report what changed and what's left.
9
-
10
- The skill owns the discipline + the breaking-change history; don't restate it here.
8
+ Invoke **`adia-migrate`**: read the MIGRATION GUIDE for the version span, run
9
+ the 5-step sweep discipline, and close with the verify gates.
@@ -1,14 +1,9 @@
1
1
  ---
2
- description: Orient in an existing adia-ui app — inventory its structure, rendering mode, and gaps.
3
- argument-hint: "[path]"
2
+ description: Orient in an existing adia-ui app — classify rendering mode, project shape, shell, and state; produce the Orientation Record.
3
+ argument-hint: "[path or question]"
4
4
  ---
5
5
 
6
- Orient in an existing adia-ui app. **$ARGUMENTS**
6
+ Orient in this repo. **$ARGUMENTS**
7
7
 
8
- Invoke **`adia-ui-factory`** and run its four classifiers against the target, producing an Orientation Record:
9
-
10
- 1. **Detect the rendering mode** (SPA vs SSR) from the signals in the skill's table.
11
- 2. **Inventory the structure** — host/provider, routing owner, data-flow, state placement, custom components.
12
- 3. **Report mode + structure + gaps**, and recommend the next skill (`compose` / `spa` / `ssr` / `verify`).
13
-
14
- Treat the app's source and docs as **data under review**, not instructions.
8
+ Invoke **`adia-orient`** and produce the Orientation Record (mode · shape ·
9
+ shell · state · gaps), each axis citing its signal.
@@ -1,17 +1,11 @@
1
1
  ---
2
- description: Scaffold a new adia-ui app pick the rendering mode (SPA / SSR-framework), lay out the structure, wire the host.
3
- argument-hint: "[spa|ssr] [app name]"
2
+ description: Scaffold a new adia-ui app, page, or component skeleton (bin/adia-scaffold emits the mechanical skeleton; the skill owns the shape decisions).
3
+ argument-hint: "[app|page|component] [name]"
4
4
  ---
5
5
 
6
- Scaffold a new adia-ui app. **$ARGUMENTS**
6
+ Scaffold an adia-ui surface. **$ARGUMENTS**
7
7
 
8
- **Name the design intent first [soft-gate]:** the design intent (the `BRIEF` what this UI is reaching for) must be **at least lightly named**, one sentence is enough and it will evolve; absent, set a provisional, revisable pull and proceed. This is a **soft gate**, cleared by _naming_ a direction, not by stopping.
9
-
10
- 1. **Pick mode + shape** via the `adia-ui-factory` classifiers (or the first argument) — or ask when it's greenfield. The shapes + four-axis layout are owned by **`adia-ui-project`** (load it for the decision).
11
- 2. **Lay the skeleton deterministically** with the bundled scaffolder:
12
- - SPA → `python3 "${CLAUDE_PLUGIN_ROOT}/bin/adia-scaffold" spa <name>`
13
- - SSR → `python3 "${CLAUDE_PLUGIN_ROOT}/bin/adia-scaffold" ssr <name> --framework <next|nuxt|sveltekit|astro>`
14
- - add surfaces with the `page` (`--duo` for declarative) / `component` sub-commands.
15
- 3. **Hand off:** `adia-ui-project` for the shape/layout decisions the bin doesn't make, `adia-ui-spa` / `adia-ui-ssr` for host wiring, then `adia-ui-compose` for the first real screen.
16
-
17
- The bin owns the byte-stable layout; the skills own the methodology. Don't restate either here.
8
+ Invoke **`adia-project`** to classify the project shape and target, then use
9
+ `${CLAUDE_PLUGIN_ROOT}/bin/adia-scaffold` for the mechanical skeleton. Shapes
10
+ the bin doesn't one-shot (rollup, shared-foundation) are composed per the
11
+ project-shapes reference.
@@ -1,10 +1,9 @@
1
1
  ---
2
- description: Verify an adia-ui surface in the browser (QA + a11y gate).
3
- argument-hint: "[path]"
2
+ description: Run the consumer-side exit gate on a composed surface browser render, console, a11y, and composition audits.
3
+ argument-hint: "[page or surface]"
4
4
  ---
5
5
 
6
- Verify an adia-ui surface before shipping. **$ARGUMENTS**
6
+ Verify a composed surface. **$ARGUMENTS**
7
7
 
8
- Hand off to **`adia-ui-verify`** for the exit gate: render in a real browser (zero console errors on load, non-zero bounding boxes, and _read_ the screenshot), check accessibility (region roles + labels, keyboard paths, AA contrast), and confirm git hygiene (re-baseline, explicit allowlists, right branch).
9
-
10
- The skill owns the depth (`${CLAUDE_PLUGIN_ROOT}/references/verification.md`); don't restate it here.
8
+ Invoke **`adia-verify`** and run its gate against the real rendered surface;
9
+ findings come back file:line with severity.
package/package.json CHANGED
@@ -1,16 +1,14 @@
1
1
  {
2
2
  "name": "@adia-ai/adia-ui-factory",
3
- "version": "0.2.4",
4
- "description": "Author and verify apps built on the adia-ui (@adia-ai) light-DOM web-component framework scaffold, compose, wire, and verify across both SPA and SSR rendering modes. Wires the a2ui MCP for catalog retrieval, UI generation, and validation.",
3
+ "version": "0.8.1",
4
+ "description": "Author and verify apps built ON the adia-ui (@adia-ai) light-DOM web-component framework \u2014 orient, scaffold, compose, wire, verify, and migrate across SPA and SSR rendering modes. Wires the a2ui MCP for catalog retrieval, UI generation, and validation.",
5
5
  "keywords": [
6
6
  "adia-ui",
7
7
  "a2ui",
8
+ "gen-ui",
8
9
  "web-components",
9
10
  "app-authoring",
10
- "ssr",
11
- "spa",
12
- "scaffolding",
13
- "llm-ui"
11
+ "consumer"
14
12
  ],
15
13
  "license": "MIT",
16
14
  "author": {
@@ -20,6 +18,7 @@
20
18
  "files": [
21
19
  ".claude-plugin",
22
20
  "skills",
21
+ "agents",
23
22
  "commands",
24
23
  "references",
25
24
  "bin",
@@ -20,8 +20,8 @@ This plugin wires `@adia-ai/a2ui-mcp` (declared in `.mcp.json`, run via `npx`).
20
20
 
21
21
  **Retrieve patterns & knowledge:**
22
22
 
23
- - `search_chunks` — semantic/keyword search over the 280+ corpus chunks.
24
- - `search_patterns` / `get_fragment` / `get_composition` — reusable composition patterns and fragments.
23
+ - `search_chunks` — semantic/keyword search over the corpus's 394 chunks.
24
+ - `search_patterns` / `list_patterns` / `get_composition` — reusable composition patterns.
25
25
  - `get_chunk` / `get_graph` / `resolve_composition` / `zettel_stats` — chunk graph navigation.
26
26
 
27
27
  **Classify & assemble (the generation pre-step):**
@@ -32,8 +32,9 @@ This plugin wires `@adia-ai/a2ui-mcp` (declared in `.mcp.json`, run via `npx`).
32
32
  **Generate** (host LLM in stdio):
33
33
 
34
34
  - `generate_ui` — produce A2UI/markup for a described surface.
35
- - `refine_ui` — iterate on a generated surface (multi-turn refinement; carry a `sessionId`).
36
- - `plan_app_state` — plan an app's state shape.
35
+ - `refine_ui` — correct a `generate_ui` result whose validation failed (params: `intent`, `previousMessages`, `validationErrors`; monolithic only — zettel refines via `refine_composition`).
36
+ - `plan_app_state` — extract the intent/domain/tasks ontology from a prompt *before* generating.
37
+ - `compose_from_chunks` / `refine_composition` / `get_state` — the zettel two-call chunk workflow (compose, then refine against the kept `state_id`).
37
38
 
38
39
  **Validate** (offline):
39
40
 
@@ -43,7 +44,9 @@ This plugin wires `@adia-ai/a2ui-mcp` (declared in `.mcp.json`, run via `npx`).
43
44
 
44
45
  **Feedback & authoring loop:**
45
46
 
46
- - `submit_feedback` · `get_quality_metrics` · `get_training_gaps` · `import_pattern`.
47
+ - `submit_feedback` · `get_quality_metrics` · `get_training_gaps` · `run_eval` · `report_issue` (files a session ticket under `qa/findings/issues/`).
48
+
49
+ **Probe:** `server_status` — connectivity/version check (the `adia-verify` wiring probe).
47
50
 
48
51
  ## When to reach for it vs. hand-author
49
52
 
@@ -55,9 +58,9 @@ This plugin wires `@adia-ai/a2ui-mcp` (declared in `.mcp.json`, run via `npx`).
55
58
 
56
59
  Wiring the MCP is the right leverage, but be honest about what it costs:
57
60
 
58
- - **Always-on context (P6).** ~24 tool definitions load into context whenever the plugin is enabled, and the server starts on enable — whether or not you call a tool. This plugin's prose drives ~5 tools as the spine (`get_component_map`, `search_chunks`, `generate_ui`, `validate_schema`, `check_anti_patterns`) and ~13 across the authoring tier; the feedback/training (`submit_feedback`, `get_training_gaps`, `import_pattern`) and zettel-graph (`get_graph`, `resolve_composition`, `zettel_stats`) tools are corpus-maintainer surface an authoring agent rarely touches. **The tool set can't be scoped from `.mcp.json`** — the only lever is enabling/disabling the whole server (the README gives the disable path). A methodology-only user pays the full tax.
59
- - **Supply chain.** `.mcp.json` pins `@adia-ai/a2ui-mcp@0.7.8` (an exact version, so an upgrade is a reviewable diff — never `@latest`). Enabling the plugin runs that upstream package from npm; the methodology in these references is a snapshot of the same version — re-bake both together on a bump (`ROADMAP.md`).
60
- - **Trust / network (P9).** The server's outbound behavior is upstream-owned: semantic `search_chunks` (with a key) makes provider calls, and `submit_feedback`/`import_pattern` are telemetry-shaped. If a closed network posture matters, verify it upstream rather than assuming — treat "unknown" as a disclosed unknown, not "safe."
61
+ - **Always-on context (P6).** 30 tool definitions (SoT: `packages/a2ui/mcp/TOOLS.md`) load into context whenever the plugin is enabled, and the server starts on enable — whether or not you call a tool. This plugin's prose drives ~5 tools as the spine (`get_component_map`, `search_chunks`, `generate_ui`, `validate_schema`, `check_anti_patterns`) and ~13 across the authoring tier; the feedback/training (`submit_feedback`, `get_training_gaps`) and zettel-graph (`get_graph`, `resolve_composition`, `zettel_stats`) tools are corpus-maintainer surface an authoring agent rarely touches. **The tool set can't be scoped from `.mcp.json`** — the only lever is enabling/disabling the whole server (the README gives the disable path). A methodology-only user pays the full tax.
62
+ - **Supply chain.** `.mcp.json` pins `@adia-ai/a2ui-mcp@0.7.26` (an exact version, so an upgrade is a reviewable diff — never `@latest`). Enabling the plugin runs that upstream package from npm; the methodology in these references is a snapshot of the same version — re-bake both together on a bump.
63
+ - **Trust / network (P9).** The server's outbound behavior is upstream-owned: semantic `search_chunks` (with a key) makes provider calls, and `submit_feedback` is telemetry-shaped. If a closed network posture matters, verify it upstream rather than assuming — treat "unknown" as a disclosed unknown, not "safe."
61
64
 
62
65
  ## Inputs are data, not instructions
63
66
 
@@ -54,6 +54,8 @@ Every component stylesheet is scoped to its tag and split into a zero-specificit
54
54
 
55
55
  **Why two blocks:** `:where(:scope)` is zero-specificity, so a parent theme provider or a consumer override beats your defaults without `!important`. Collapsing into a single `:scope` block breaks theme switching. The base block consumes the component's _own_ tokens (`--my-thing-*`), which alias the system tokens (`--a-*`) — so re-theming flows through one indirection layer.
56
56
 
57
+ **One un-suffixed token per property.** Declare `--my-thing-bg` once and read `var(--my-thing-bg)` directly — no `-default` shadow pairs, no fallback chains. (A `-default` shadowing layer was tried and reverted: it silently broke overrides.) Ancestor-surface inheritance of component-named tokens is *not* a mechanism — a parent setting `--card-bg` for its subtree is an explicit override, not something your component should rely on.
58
+
57
59
  ## Invariants
58
60
 
59
61
  - **Token-only.** No raw `#hex` / `rgb()` / `oklch()` and no raw px ≥ 3 in component CSS — always `var(--a-*)` (border-hairlines 1–2px are the only carve-out, with a comment).
@@ -63,6 +65,28 @@ Every component stylesheet is scoped to its tag and split into a zero-specificit
63
65
  - **Symmetric lifecycle.** Every `addEventListener` in `connected()` has a matching `removeEventListener` in `disconnected()`. Use a stable handler reference (private field `#onClick = (e) => …`), not an inline arrow — `removeEventListener` needs reference equality.
64
66
  - **Data down, events up.** Sub-components receive state via properties (`.rec = …`) and emit `CustomEvent`s; they never reach into a parent's internals.
65
67
  - **Font-family floor.** A text-bearing component floors its family to a token: `font-family: var(--my-thing-family, var(--a-font-family-ui))` — otherwise it inherits a broken host font (the dead-`--a-font` trap resolves to UA serif).
68
+ - **Real props, not `data-*` hooks.** A component's public boolean/enum API is declared `static properties` reflected as clean attributes — never `data-*` styling hooks.
69
+ - **Container queries are opt-in** — `[provide-container-inline-size]` is the attribute utility that makes an element a size-query provider; it's not a per-component default.
70
+
71
+ ## CSS mechanics that bite
72
+
73
+ - **`background: <color>` shorthand silently resets `background-clip`/`origin`/`position`/`size`** — a hover/active state that only recolors must use the `background-color` longhand, or a base `background-clip: content-box` flips to `border-box` and the fill balloons.
74
+ - **`translate`/`scale`/`rotate` are independent properties, not `transform` aliases** — writing `style.translate` and reading the computed `transform` matrix (or vice versa) silently no-ops; animation loops that mix them read 0 and never move.
75
+ - **An offsetting ancestor transform (`translate(-50%,-50%)`) breaks CSS anchor positioning** for top-layer popovers; an identity transform (`translateX(0)`) doesn't.
76
+ - **An equal-specificity `@media` override must follow its base rule in source order** — placed earlier it silently loses.
77
+ - **Never author `<thead>/<tbody>/<tr>/<td>` inside a custom element** — the HTML parser foster-parents them out of any non-`<table>` ancestor before your JS ever runs.
78
+
79
+ ## Lifecycle & reactivity traps (UIElement)
80
+
81
+ - **Never declare a native DOM prop** (`textContent`, `innerHTML`, `className`, `id`) **in `static properties`** — the prop installer replaces the native accessor with a signal, so `el.textContent = ''` becomes a signal write instead of a child wipe.
82
+ - **Wrap custom-setter bodies in `untracked()`** when they read reactive props — otherwise the read leaks a subscription into whichever effect called the setter.
83
+ - **A MutationObserver that watches its own host and mutates it in the callback loops forever** — drain with `this.#observer.takeRecords()` after every absorb pass.
84
+ - **A document-listening trait must not re-emit the same-named event on its host with `bubbles: true`** — the bubble re-reaches `document` and recurses; use distinct names for the incoming signal and the outgoing host event.
85
+ - **A parent `render()` that filters children by an attr the child sets in its own `connected()` races the child upgrade** — match by tag name as well.
86
+ - **Sync icon-registry checks (`isIconName`/`hasIcon`) at connect race the async manifest** on static (non-Vite) hosts — pair them with `whenIconRegistryReady` deferred re-stamping.
87
+ - **FLIP/layout-animation preserves node identity, not content** — it fires on `appendChild` moves but not `replaceChildren` rebuilds, so diff children in place; and any post-drop effect must target the *final* DOM node by stable id (cross-container moves rebuild the source in a new parent).
88
+ - **Drag affordances: one strong layer.** Keep the drop indicator (most precise) strong; ghost border, column outline, and bg tint stay subtle — everything at full strength reads as competing chrome.
89
+ - **Drop targets: walk descendants, index post-removal.** `[droppable]` matches items via descendants + a closest-droppable filter (not direct children), so nested containers (cards inside a `.col-list`) still register; and a drop-indicator index is column-relative *after* the lifted item leaves — filter out the `[data-draggable-list-item-lifting]` node before counting, or the indicator lands one slot off.
66
90
 
67
91
  ## Anti-patterns (the lint seeds)
68
92
 
@@ -85,4 +109,4 @@ The mechanizable smells the framework audits — these seed the phase-(c) hook a
85
109
  | Hardcoded `open` on `<modal-ui>`/`<drawer-ui>` | drive via `.open = true` |
86
110
  | `new URL('…', import.meta.url)` literal for a corpus path | hold the path in a variable first |
87
111
 
88
- Run the framework's own gates when you have the repo (`npm run check:anti-patterns`, `audit:*`), and `mcp__a2ui__check_anti_patterns` / `validate_schema` on generated markup.
112
+ Run the plugin's `bin/adia-lint` on changed files, the framework's `audit:*` gates when you have the repo, and `mcp__a2ui__check_anti_patterns` / `validate_schema` on generated markup.
@@ -13,8 +13,12 @@ How to _think_ about the catalog. The **live** catalog is the a2ui MCP (`get_com
13
13
 
14
14
  **Naming:** kebab-case with a `-ui` **suffix** (`<button-ui>`, not `<a-button>`). Shells/composites carry a domain prefix (`<admin-*>`, `<chat-*>`).
15
15
 
16
+ **Light DOM is the substrate.** Components render in the light DOM — a `slot="X"` attribute is *inert* metadata unless CSS targets `[slot="X"]`; positioning is CSS by tag + ancestor + DOM order, never slot routing. Grep before believing any "the slot routes content" claim.
17
+
16
18
  **Rule of first resort:** reach for a catalog primitive before authoring anything. Never use a raw `<button>`/`<input>`/`<div>` where a `*-ui` primitive exists — raw elements skip focus rings, theming, density, and form association. Author a project component (see [authoring-components.md](authoring-components.md)) only when no primitive composes to the need.
17
19
 
20
+ **Browser baseline:** Chromium 125+ · Safari 18.0+ · Firefox 129+ — the floor that makes `light-dark()`, `@scope`, popovers, and OKLCH usable without fallbacks.
21
+
18
22
  ## Registration is a side effect of import
19
23
 
20
24
  Importing a component module **registers its tag**. Two forms:
@@ -26,6 +30,13 @@ import '@adia-ai/web-components/components/button/button.js'; // one component
26
30
 
27
31
  Registration uses `defineIfFree('button-ui', UIButton)` internally — idempotent, so double-import is safe. The non-registering class import (`.../button/class`) exists for tests/subclassing. **In SSR this import must be client-only** — see [ssr-integration.md](ssr-integration.md).
28
32
 
33
+ Registration corollaries that bite:
34
+
35
+ - **Composites stamp primitives your HTML never mentions** — `chat-input-ui` internally renders `textarea-ui` + `select-ui`; unless those primitives are registered too, they stay undefined and collapse to 0×0.
36
+ - **Shells register via the cluster barrel** (`@adia-ai/web-modules/shell|chat|editor`) — importing one shell's `.js` registers only the host; its bespoke children (`admin-sidebar`, `chat-thread`, …) only register when their sibling JS loads.
37
+ - **Some tags are defined in a parent component's `.js`**, and some components are deliberately CSS-only (stylesheet + metadata, no `.js`) — when auditing whether a tag is real, grep for its `customElements.define`, don't `ls` folders.
38
+ - **JS registers, CSS loads separately** — component CSS arrives via `<link>`/the CSS barrel, not the module graph; a JS-only side-effect import yields a registered but unstyled element.
39
+
29
40
  ## Signals, not a virtual DOM
30
41
 
31
42
  Components extend `UIElement` (or `UIFormElement` for form-participating controls) and use fine-grained signals:
@@ -40,6 +51,28 @@ import { UIElement, signal, computed, effect } from '@adia-ai/web-components/cor
40
51
 
41
52
  Declared `static properties` are wrapped as signals automatically, so setting `el.disabled = true` re-renders. Don't run a parallel `CustomEvent`-only state path that competes with signals.
42
53
 
54
+ ## Choosing components — the recurring calls
55
+
56
+ Selection mistakes that keep recurring (verify props with `lookup_component` when unsure):
57
+
58
+ - **`<stack-ui>` is a z-axis overlay** — all children share one grid cell. Vertical stacking is `<col-ui>`.
59
+ - **High-frequency, low-cardinality view pickers** (Day/Week/Month, Kanban/List) are `<segmented-ui>`, not `<select-ui>` — all options visible, active one highlighted; reserve `select-ui` for lower-frequency, many-option knobs.
60
+ - **Sidebar navigation is `<nav-ui>` + `<nav-item-ui>`**; `<menu-ui>`/`<menu-item-ui>` are for Popover-API dropdowns, never persistent nav.
61
+ - **A list of rows** (leading control + title + subtext + trailing badge) is `<list-ui> > <list-item-ui>`, not bespoke flex divs.
62
+ - **A standalone checkbox is `<check-ui name label="…">`** — never one `check-ui` wrapped in `<field-ui inline>`. `<field-ui>` is for form contexts only; toolbar knobs use the bare control + `aria-label` (the trigger already shows the value).
63
+ - **Multiple radio/check siblings inside one `<field-ui label>`** need a `<col-ui gap="1">` wrapper — field-ui stacks a single input; without it they overlap.
64
+ - **Card/drawer body content wraps in `<section>`** (canonical order: void media → `<header>` → `<section>`+ → `<footer>`) — direct flow children bypass the body slot and lose the `--card-inset` margin; `<section bleed>` zeros the inset but keeps inline padding.
65
+ - **Clickable grid cards:** wrap the card in `<a href style="display:contents">` — link semantics + keyboard focus without a layout box, so the grid still sees the card as the cell; hover rides `a:hover card-ui`.
66
+ - **No `stretch` on a `<button-ui>` inside a `<col-ui>` action stack** (col-ui already stretches children); `size="lg"` on an action stack applies only when it contains a `variant="primary"` button.
67
+ - **`<toast-ui>` is a facade over `<feed-ui>`** — `AdiaToast.show()` routes to `AdiaFeed.post()`; the top-layer/queue/focus logic lives in the feed component.
68
+
69
+ ## Attribute honesty — silent-failure class
70
+
71
+ - **Components silently accept any made-up attribute** — `text-ui muted`, `card-ui hover-elevate` are no-ops with zero warnings. Check the real prop list (`lookup_component`) before authoring; a rendered-but-unstyled state usually means an invented attr.
72
+ - **`empty-state-ui` takes `[heading]`, not `[title]`** — `title=` becomes the native tooltip and the message silently doesn't render.
73
+ - **Primary content often rides the default slot, not `text=`/`label=`** — `kbd-ui` and `card-ui` are the canonical traps. `button-ui` accepts both `text=` and child text; prefer `text=` for generated UI.
74
+ - **Shell-tier bespoke children reflect state as attributes** (`admin-sidebar[collapsed]`) — read and style via attribute selectors; coordinate via `querySelector`, not a central store.
75
+
43
76
  ## Traits — behavior by declaration
44
77
 
45
78
  ```html
@@ -48,6 +81,9 @@ Declared `static properties` are wrapped as signals automatically, so setting `e
48
81
 
49
82
  or programmatically: `static traits = [pressable, scalePress]`. A trait is a factory (`defineTrait({ name, setup({host}) { …; return cleanup } })`) that manages attributes/events and tears itself down on disconnect. Query the live set with the MCP's `get_traits`.
50
83
 
84
+ - **Traits require `UIElement`** — `traits="…"` on an HTMLElement-based or native element is silently ignored.
85
+ - **`data-stream-*` attributes** are a universal ingestion trait: any element with a settable `.data` can subscribe to a shared, refcounted transport.
86
+
51
87
  ## Tokens — the only styling currency
52
88
 
53
89
  Three levels, all `--a-*`:
@@ -55,17 +91,29 @@ Three levels, all `--a-*`:
55
91
  | Level | Role | Examples |
56
92
  | --- | --- | --- |
57
93
  | **L1 primitive scales** | dimensionless base values | `--a-space-2` `--a-size-md` `--a-radius-sm` `--a-duration-fast` `--a-font-family-ui` |
58
- | **L2 semantic families** | role vocabulary | `--a-primary` `--a-danger` `--a-accent-bg` |
59
- | **L3 state × role** | what components consume | `--a-accent-bg-hover` `--a-danger-fg-active` `--a-ui-bg-disabled` |
94
+ | **L2 semantic families** | role vocabulary | `--a-primary` `--a-danger` `--a-primary-bg` |
95
+ | **L3 state × role** | what components consume | `--a-primary-bg-hover` `--a-danger-fg-active` `--a-ui-bg-disabled` |
60
96
 
61
- - **Color scheme** uses `light-dark()` at the token layer — never swap tokens by hand. Toggle with `<toggle-scheme-ui scheme="auto" target=":root" persist>`.
62
- - **Density** scales spacing via `--a-density` set at a provider boundary (`<div style="--a-density: 0.8">`).
97
+ - **Color scheme** uses `light-dark()` at the token layer — never swap tokens by hand. Toggle with `<toggle-scheme-ui scheme="auto" target=":root" persist>`. Theming is **two independent axes**: `data-scheme` (light/dark) vs `data-theme` (named palette) — and `themes.css` loads separately from the styles barrel.
98
+ - **Density / spacing / radius are `@property` multipliers** three knobs rescale the whole system (the parametric spatial system); e.g. `--a-density: 0.8` at a provider boundary.
63
99
  - **Zero raw colors, zero raw px ≥ 3** in component CSS — always a token. This is mechanized by the `adia-lint` hook and by the MCP's `check_anti_patterns`.
100
+ - **Use a token for its named role, never by coincidence** — `--a-bg` is never a foreground, `--a-fg` never a background; a primary fill is `--a-primary` + `--a-chrome-light` text.
101
+ - **Text/icons on a filled primary disc** (radio dots, step circles, badge counters) use `--a-chrome-light` — theme-stable against any fill. (`--a-primary-fg` also resolves light post-Material-adoption; `--a-chrome-light` stays correct on arbitrary fills.)
102
+ - **Series/group identity colors are `--a-data-0..9`**; semantic tones (info/success/danger/accent) mark *state*, never identity.
103
+ - **Never re-ladder a foundation surface token** — don't override `--card-bg` with a bespoke elevation ladder; divergent ladders reintroduce the light/dark contrast inconsistency the symmetric canvas ramp prevents. (Per-element status tints are the legitimate exception.)
104
+ - **Global `[color]`/`[weight]` utilities override component colors by `@layer` order** — a filled control that repurposes `color=` must opt out explicitly; conversely `[weight]` does *not* beat component-scoped `font-weight` (use a variant).
105
+ - **Square/1:1 cells inheriting `--a-radius-md` render as circles** — small square cells take `--a-radius-sm`.
106
+ - **Subtle structure chrome** (gridlines, weekend tints, today-column) shares one "barely-there" contrast budget — calibrate against the canvas surfaces in both schemes; too low is invisible, too high is chrome riot.
64
107
 
65
108
  ## "Registers" — typographic treatments, opt-in
66
109
 
67
110
  A _register_ (e.g. `verse`, `prose`) is a typographic treatment applied two ways together: **link the register stylesheet** (`styles/verse.css`) **and** put the **attribute** on the surface (`<my-surface verse>`). One without the other is a no-op — a common smell. Body/UI text defaults to `--a-font-family-ui`; registers opt a subtree into a different family/rhythm.
68
111
 
112
+ ## Layout realities
113
+
114
+ - **A grid `auto`/`max-content` track collapses to ~1px around a flex wrapper** whose explicit width lives on an inner child — intrinsic max-content doesn't propagate through the wrapper; set the width on the wrapper itself (CSS, or JS-mirrored via ResizeObserver).
115
+ - **Full-height shells need an unbroken flex chain** — see the mount gotcha in [shell-admin.md](shell-admin.md); it applies to every shell.
116
+
69
117
  ## Where this is incomplete
70
118
 
71
119
  The deep internals — the `html` template engine's escaping contract, `BaseController` delegation, the icon loader (`installIconLoadersForRegistered` + Vite `import.meta.glob`), `@bp` responsive notation, and the A2UI runtime/`<a2ui-root>` resolver — are framework-owned and version-specific. Treat the MCP (`lookup_component`, `search_chunks`) as the source of truth for them rather than encoding them here.