@adia-ai/adia-ui-forge 0.8.55 → 0.8.57
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.
- package/.claude-plugin/plugin.json +3 -2
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +40 -0
- package/README.md +1 -1
- package/__init__.py +5 -0
- package/commands/demo-audit.md +1 -1
- package/commands/gen-ui-review.md +1 -1
- package/commands/package-release.md +1 -1
- package/commands/site-deployment.md +1 -1
- package/package.json +1 -1
- package/plugin.yaml +1 -1
- package/prompts/demo-audit.md +1 -1
- package/prompts/gen-ui-review.md +1 -1
- package/prompts/package-release.md +1 -1
- package/prompts/site-deployment.md +1 -1
- package/scripts/forge-lint.mjs +168 -0
- package/scripts/lint-rules.generated.mjs +1700 -0
- package/scripts/site-postwrite-derivation-gate +23 -127
- package/skills/a2ui-maintenance/SKILL.md +1 -1
- package/skills/a2ui-maintenance/references/data-model-reactivity.md +49 -29
- package/skills/a2ui-maintenance/references/pipeline-overview.md +58 -22
- package/skills/a2ui-maintenance/references/surface-lifecycle.md +14 -7
- package/skills/component-md-authoring/SKILL.md +116 -0
- package/skills/component-md-authoring/agents/openai.yaml +3 -0
- package/skills/demo-audit/references/visual-probe-triage.md +4 -1
- package/skills/gen-ui-review/SKILL.md +4 -1
- package/skills/gen-ui-review/references/loop-protocol.md +6 -5
- package/skills/package-release/references/changelog-discipline.md +6 -3
- package/skills/package-release/references/cut-procedure.md +61 -14
- package/skills/package-release/references/gates-catalog.md +6 -2
- package/skills/package-release/references/recovery-paths.md +6 -3
- package/skills/package-release/scripts/gate-roster.mjs +10 -7
- package/skills/package-release/scripts/release-pack.mjs +289 -17
- package/skills/primitive-authoring/references/anti-patterns.md +2 -2
- package/skills/primitive-authoring/references/api-contract.md +17 -4
- package/skills/primitive-authoring/references/authoring-cycle.md +1 -1
- package/skills/primitive-authoring/references/code-style.md +1 -1
- package/skills/primitive-authoring/references/common-gotchas.md +6 -6
- package/skills/primitive-authoring/references/form-control-sizing.md +22 -7
- package/skills/primitive-authoring/references/token-contract.md +5 -0
- package/skills/primitive-authoring/references/yaml-contract.md +132 -14
- package/skills/site-docs-authoring/SKILL.md +7 -9
- package/hooks/hooks.json +0 -44
- package/scripts/forge-lint +0 -315
|
@@ -1,88 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""site-postwrite-derivation-gate — PostToolUse check: site source edits name their stale derivatives.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
(fail-open — this hook must never block editing with a crash).
|
|
4
|
+
site/sitemap.json feeds GENERATED surfaces (site/llms.txt, the patterns/
|
|
5
|
+
templates index) that go stale the moment a route or content path changes
|
|
6
|
+
without a regen. This hook feeds that regeneration obligation back at
|
|
7
|
+
write-time.
|
|
8
|
+
|
|
9
|
+
(Before ADR-0072 Decision 2 / gh#2410, this hook also flagged
|
|
10
|
+
site/pages/**/*.html fragment edits whose route the site-a2ui ledger
|
|
11
|
+
marked "converted" — the docs site rendered such routes from a GENERATED
|
|
12
|
+
site-a2ui/pages/<slug>.a2ui.json artifact, so a fragment-only edit changed
|
|
13
|
+
nothing users saw. That render path retired; every route now renders
|
|
14
|
+
straight from its fragment, so a fragment edit is never stale on its own
|
|
15
|
+
and needs no nudge.)
|
|
17
16
|
|
|
18
17
|
Usage:
|
|
19
18
|
site-postwrite-derivation-gate --hook # PostToolUse mode: event JSON on stdin
|
|
20
|
-
site-postwrite-derivation-gate selftest # prove matcher
|
|
19
|
+
site-postwrite-derivation-gate selftest # prove matcher on fixtures
|
|
21
20
|
"""
|
|
22
21
|
import json
|
|
23
22
|
import sys
|
|
24
23
|
|
|
25
24
|
SITEMAP_MARKER = "site/sitemap.json"
|
|
26
|
-
FRAGMENT_MARKER = "site/pages/"
|
|
27
|
-
|
|
28
|
-
# Generated files under site/pages/ are sidecar-prewrite-guard territory.
|
|
29
|
-
GENERATED_EXCLUDE = ("site/pages/patterns/index.html",)
|
|
30
25
|
|
|
31
26
|
|
|
32
27
|
def classify(path):
|
|
33
|
-
"""→
|
|
34
|
-
if
|
|
35
|
-
return
|
|
36
|
-
if path.endswith(SITEMAP_MARKER):
|
|
37
|
-
return "sitemap", path[: -len(SITEMAP_MARKER)], "/" + SITEMAP_MARKER
|
|
38
|
-
idx = path.find(FRAGMENT_MARKER)
|
|
39
|
-
if idx == -1 or not path.endswith(".html") or path[idx:] in GENERATED_EXCLUDE:
|
|
40
|
-
return None, None, None
|
|
41
|
-
return "fragment", path[:idx], "/" + path[idx:]
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def find_route(sitemap, rel):
|
|
45
|
-
"""Walk sections/items for the entry whose `content` equals rel; → route path or None."""
|
|
46
|
-
stack = [sitemap.get("sections") or []]
|
|
47
|
-
while stack:
|
|
48
|
-
node = stack.pop()
|
|
49
|
-
if isinstance(node, list):
|
|
50
|
-
stack.extend(node)
|
|
51
|
-
elif isinstance(node, dict):
|
|
52
|
-
if node.get("content") == rel and node.get("path"):
|
|
53
|
-
return node["path"]
|
|
54
|
-
stack.extend(v for k, v in node.items() if k in ("sections", "items", "children"))
|
|
28
|
+
"""→ "sitemap" if `path` is site/sitemap.json, else None."""
|
|
29
|
+
if path and path.endswith(SITEMAP_MARKER):
|
|
30
|
+
return "sitemap"
|
|
55
31
|
return None
|
|
56
32
|
|
|
57
33
|
|
|
58
|
-
def resolve_fragment(root, rel):
|
|
59
|
-
"""→ feedback reason for a stale-artifact edit, or None to stay quiet.
|
|
60
|
-
|
|
61
|
-
Raises on I/O / parse trouble — the caller fail-opens.
|
|
62
|
-
"""
|
|
63
|
-
with open(root + "site/sitemap.json", encoding="utf-8") as f:
|
|
64
|
-
route = find_route(json.load(f), rel)
|
|
65
|
-
if not route:
|
|
66
|
-
return None # not a routed page
|
|
67
|
-
with open(root + "site-a2ui/ledger.json", encoding="utf-8") as f:
|
|
68
|
-
rows = json.load(f).get("pages") or []
|
|
69
|
-
row = next((r for r in rows if r.get("route") == route), None)
|
|
70
|
-
if not row or row.get("status") != "converted":
|
|
71
|
-
return None # legacy-rendered: the fragment IS what users see
|
|
72
|
-
slug = row.get("slug") or route.lstrip("/").replace("/", "__")
|
|
73
|
-
return (
|
|
74
|
-
f"site-postwrite-derivation-gate · {rel} is the SOURCE for {route}, which renders "
|
|
75
|
-
f"from GENERATED site-a2ui/pages/{slug}.a2ui.json — the artifact is now stale and "
|
|
76
|
-
f"users still see the OLD content. Regenerate: `node scripts/build/site-a2ui.mjs "
|
|
77
|
-
f"--page {route}` (repo-wide check: `npm run verify:site-a2ui`)."
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
|
|
81
34
|
SITEMAP_REASON = (
|
|
82
35
|
"site-postwrite-derivation-gate · site/sitemap.json feeds GENERATED surfaces that are "
|
|
83
36
|
"now stale: site/llms.txt (`npm run build:llms`) and the patterns/templates index "
|
|
84
|
-
"(`npm run build:patterns-index`).
|
|
85
|
-
"site-a2ui route coverage: `node scripts/build/site-a2ui.mjs --verify`."
|
|
37
|
+
"(`npm run build:patterns-index`)."
|
|
86
38
|
)
|
|
87
39
|
|
|
88
40
|
|
|
@@ -92,80 +44,24 @@ def hook_mode():
|
|
|
92
44
|
except Exception:
|
|
93
45
|
return 0
|
|
94
46
|
tool_input = event.get("tool_input") or {}
|
|
95
|
-
|
|
96
|
-
if not kind:
|
|
97
|
-
return 0
|
|
98
|
-
if kind == "sitemap":
|
|
47
|
+
if classify(tool_input.get("file_path") or "") == "sitemap":
|
|
99
48
|
print(json.dumps({"decision": "block", "reason": SITEMAP_REASON}))
|
|
100
|
-
return 0
|
|
101
|
-
try:
|
|
102
|
-
reason = resolve_fragment(root, rel)
|
|
103
|
-
except Exception:
|
|
104
|
-
return 0 # fail-open: never block editing on our own error
|
|
105
|
-
if reason:
|
|
106
|
-
print(json.dumps({"decision": "block", "reason": reason}))
|
|
107
49
|
return 0
|
|
108
50
|
|
|
109
51
|
|
|
110
52
|
def selftest():
|
|
111
|
-
import os
|
|
112
|
-
import tempfile
|
|
113
|
-
|
|
114
53
|
scope_cases = [
|
|
115
|
-
("/repo/site/pages/architecture/ontology.html", "fragment"),
|
|
116
|
-
("site/pages/guides/testing.html", "fragment"),
|
|
117
54
|
("/repo/site/sitemap.json", "sitemap"),
|
|
118
|
-
("/repo/site/pages/
|
|
119
|
-
("
|
|
120
|
-
("/repo/site
|
|
55
|
+
("/repo/site/pages/architecture/ontology.html", None),
|
|
56
|
+
("site/pages/guides/testing.html", None),
|
|
57
|
+
("/repo/site/pages/patterns/index.html", None),
|
|
121
58
|
("/repo/site/site.js", None),
|
|
122
59
|
("/repo/site/pages/architecture/notes.txt", None),
|
|
123
60
|
("/repo/apps/tasks/app/index.html", None),
|
|
124
61
|
]
|
|
125
62
|
for path, expected in scope_cases:
|
|
126
|
-
if classify(path)
|
|
127
|
-
print(f"selftest: FAIL scope {path} → {classify(path)
|
|
128
|
-
return 1
|
|
129
|
-
with tempfile.TemporaryDirectory() as tmp:
|
|
130
|
-
root = tmp + os.sep
|
|
131
|
-
os.makedirs(root + "site")
|
|
132
|
-
os.makedirs(root + "site-a2ui")
|
|
133
|
-
with open(root + "site/sitemap.json", "w", encoding="utf-8") as f:
|
|
134
|
-
json.dump(
|
|
135
|
-
{
|
|
136
|
-
"sections": [
|
|
137
|
-
{
|
|
138
|
-
"items": [
|
|
139
|
-
{"path": "/site/a/one", "content": "/site/pages/a/one.html"},
|
|
140
|
-
{"path": "/site/a/two", "content": "/site/pages/a/two.html"},
|
|
141
|
-
]
|
|
142
|
-
}
|
|
143
|
-
]
|
|
144
|
-
},
|
|
145
|
-
f,
|
|
146
|
-
)
|
|
147
|
-
with open(root + "site-a2ui/ledger.json", "w", encoding="utf-8") as f:
|
|
148
|
-
json.dump(
|
|
149
|
-
{
|
|
150
|
-
"pages": [
|
|
151
|
-
{"route": "/site/a/one", "slug": "site__a__one", "status": "converted"},
|
|
152
|
-
{"route": "/site/a/two", "slug": "site__a__two", "status": "failed"},
|
|
153
|
-
]
|
|
154
|
-
},
|
|
155
|
-
f,
|
|
156
|
-
)
|
|
157
|
-
resolve_cases = [
|
|
158
|
-
("/site/pages/a/one.html", True), # converted → nudge
|
|
159
|
-
("/site/pages/a/two.html", False), # ledger row not converted → quiet
|
|
160
|
-
("/site/pages/a/three.html", False), # no sitemap entry → quiet
|
|
161
|
-
]
|
|
162
|
-
for rel, should_fire in resolve_cases:
|
|
163
|
-
fired = resolve_fragment(root, rel) is not None
|
|
164
|
-
if fired != should_fire:
|
|
165
|
-
print(f"selftest: FAIL resolve {rel} fired={fired} expected={should_fire}")
|
|
166
|
-
return 1
|
|
167
|
-
if "site__a__one.a2ui.json" not in resolve_fragment(root, "/site/pages/a/one.html"):
|
|
168
|
-
print("selftest: FAIL reason does not name the stale artifact")
|
|
63
|
+
if classify(path) != expected:
|
|
64
|
+
print(f"selftest: FAIL scope {path} → {classify(path)} expected {expected}")
|
|
169
65
|
return 1
|
|
170
66
|
print("selftest: PASS")
|
|
171
67
|
return 0
|
|
@@ -47,7 +47,7 @@ Unmatched work defaults to pipeline-overview and re-classifies from there.
|
|
|
47
47
|
| Tune the anti-pattern catalogue | [anti-patterns](references/anti-patterns.md) |
|
|
48
48
|
| A contract can't express a content shape — decide how to extend it | [format-extension-decisions](references/format-extension-decisions.md) |
|
|
49
49
|
| Surface regeneration, pending/stale rendering, the `doc`-setter bracket | [surface-lifecycle](references/surface-lifecycle.md) (ADR-0061) |
|
|
50
|
-
| Data-model internals — `Cell`/`Derived`, RFC-6901 pointers,
|
|
50
|
+
| Data-model internals — `Cell`/`Derived`, RFC-6901 pointers, watch semantics (shipped) | [data-model-reactivity](references/data-model-reactivity.md) (ADR-0078) |
|
|
51
51
|
|
|
52
52
|
## Contracts that gate every change
|
|
53
53
|
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# v1 data-model reactivity — Cell/Derived, RFC-6901 pointers, value-identity cutoff
|
|
2
2
|
|
|
3
3
|
Source of truth: [ADR-0078](../../../../../../docs/ops/adr/adr-0078-a2ui-runtime-adopts-v1-data-model.md)
|
|
4
|
-
(ratified 2026-08-20, gh#1762; phased plan tracked in gh#1784
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
(ratified 2026-08-20, gh#1762; phased plan tracked in gh#1784, **closed
|
|
5
|
+
2026-08-28 — all four phases P1-P4 shipped**). Read the ADR before touching
|
|
6
|
+
`packages/gen-ui/a2ui`'s data-model internals or `surface.js`/`renderer.js`
|
|
7
|
+
watch semantics — this file is the routing pointer + the shape of the
|
|
8
|
+
adopted contract, not a restatement of the ruling. `path-pointer.js` (the
|
|
9
|
+
transitional three-walker shim gh#1763 staged) is deleted as of P4; every
|
|
10
|
+
call site now uses the vendored `resolvePointer`/`setPointer`/`deletePointer`/
|
|
11
|
+
`createDataModel` primitives directly.
|
|
10
12
|
|
|
11
|
-
## What's decided (
|
|
13
|
+
## What's decided (shipped — ADR-0078 P1-P4 complete, gh#1784 closed)
|
|
12
14
|
|
|
13
15
|
The in-repo A2UI runtime (`packages/gen-ui/a2ui`, the 0.9 dialect) **adopts**
|
|
14
16
|
the vendored `packages/genui` v1.0 data model internally — `Cell`/`Derived`
|
|
@@ -19,8 +21,11 @@ data model. This is an internal implementation swap, not a consumer
|
|
|
19
21
|
migration or a wire-grammar change: the dialect's eight message kinds, the
|
|
20
22
|
`{path}` binding-prop shape, `updateDataModel`, `HandlerContext.updateModel/
|
|
21
23
|
setModel`, `registerController/Handler/Resolver`, and the `<a2ui-root>`
|
|
22
|
-
element API are all unchanged
|
|
23
|
-
|
|
24
|
+
element API are all unchanged. **[amended 2026-08-28, gh#2268/lld-0005,
|
|
25
|
+
gh#2212]** Decision item 2's falsifier is narrowed to wire-GRAMMAR diffs
|
|
26
|
+
only: it is falsified if any consumer needs a code change, or if
|
|
27
|
+
`dialect-schema.source.mjs`'s wire grammar diffs — a bare `$id`/filename
|
|
28
|
+
identifier rename in `dialect-schema.source.mjs` no longer falsifies it.
|
|
24
29
|
Phased across future cuts, plan/LLD to follow — nothing rides in the cut
|
|
25
30
|
this ADR itself was ratified for (item 6).
|
|
26
31
|
|
|
@@ -49,19 +54,25 @@ this ADR itself was ratified for (item 6).
|
|
|
49
54
|
same identity-cutoff rule as R-R10). This supersedes `surface.js`'s
|
|
50
55
|
prefix-descend rule and `renderer.js`'s re-apply-all-bindings behavior.
|
|
51
56
|
|
|
52
|
-
## What
|
|
57
|
+
## What converged (P1-P4, complete)
|
|
53
58
|
|
|
54
|
-
`path-pointer.js` (gh#1763)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
`/name/length` on a string
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
`path-pointer.js` (gh#1763) preserved three divergent legacy walkers
|
|
60
|
+
(`getByPath`/`setByPath`, `getPath`/`setPath`, `getModelValue`) as a
|
|
61
|
+
deliberate staging step; each call site migrated to the vendored semantics
|
|
62
|
+
above in turn (P2: `renderer.js`; P3: `surface.js`/`wiring-registry.js`),
|
|
63
|
+
and P4 deleted the module once its last call site migrated — no divergent
|
|
64
|
+
walker survives (Decision 4). Every migration phase named its own behavior
|
|
65
|
+
deltas rather than changing silently (ADR-0078 item 4's falsifier): `/a//b/`
|
|
66
|
+
stopped resolving forgivingly, `/name/length` on a string stopped leaking
|
|
67
|
+
the primitive's own property, `~0`/`~1` escaping is now honored, and a2ui's
|
|
68
|
+
own read/write asymmetry (previously: `getByPath`/`getPath` treated
|
|
69
|
+
absent-path/`""`/`"/"` alike, but `setByPath`/`setPath` no-op'd on root
|
|
70
|
+
instead of replacing it) converged on the read/write split named above. The
|
|
71
|
+
full Δ1-Δ10 delta table lives in
|
|
72
|
+
[lld-0001-a2ui-data-model-consumption §Data](../../../../../../docs/ops/lld/lld-0001-a2ui-data-model-consumption.md#data);
|
|
73
|
+
the surviving regression proof for the pointer-only deltas is
|
|
74
|
+
`packages/gen-ui/a2ui/data-model-pointer-semantics.test.js` (migrated from
|
|
75
|
+
`path-pointer.test.js` in P4).
|
|
65
76
|
|
|
66
77
|
## What stays fixed (don't "fix" these under this ADR)
|
|
67
78
|
|
|
@@ -76,15 +87,24 @@ instead of replacing it) converges on the read/write split named above.
|
|
|
76
87
|
- `record.js`'s bidirectional-overlap store — app-layer, outside this
|
|
77
88
|
package, rides a separate review track (R2), not this ADR.
|
|
78
89
|
|
|
79
|
-
## Consumption mechanism —
|
|
90
|
+
## Consumption mechanism — decided (build-time copy)
|
|
80
91
|
|
|
81
|
-
`@adia-ai/a2ui` is a zero-runtime-deps package (ADR-0048 posture)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
92
|
+
`@adia-ai/a2ui` is a zero-runtime-deps package (ADR-0048 posture).
|
|
93
|
+
**[amended 2026-08-29, ADR-0096, gh#2373 — recorded in ADR-0078's own
|
|
94
|
+
2026-08-29 amendment]**
|
|
95
|
+
`packages/genui` is absorbed first-party in-repo source, not a vendored
|
|
96
|
+
artifact — `packages/genui/VENDOR.json` and the vendor-and-sync mechanism
|
|
97
|
+
are gone (gh#2372 closed; `VENDOR.json` confirmed absent from origin/main).
|
|
98
|
+
[lld-0001](../../../../../../docs/ops/lld/lld-0001-a2ui-data-model-consumption.md)
|
|
99
|
+
decided a build-time byte-identical copy of
|
|
100
|
+
`packages/genui/renderer/dist/data-model.js` (+`.d.ts`) into
|
|
101
|
+
`packages/gen-ui/a2ui/`, with a `data-model.provenance.json` sidecar and a
|
|
102
|
+
freshness gate in `npm run check` — preserving the zero-deps posture (no new
|
|
103
|
+
package dependency, no covert workspace-import). The provenance sidecar now
|
|
104
|
+
points at the in-repo source path instead of a `VENDOR.json` sha: it stamps
|
|
105
|
+
`source.js`/`source.dts` + `syncedAt` + `contentHash` only —
|
|
106
|
+
`packages/genui/renderer/dist/data-model.js` — with no vendor-sha field at
|
|
107
|
+
all.
|
|
88
108
|
|
|
89
109
|
## Eval-floor risk
|
|
90
110
|
|
|
@@ -4,34 +4,65 @@
|
|
|
4
4
|
|
|
5
5
|
Two protocol layers coexist (ADR-0059, `docs/ops/spec/spec-a2ui-v1-conformance.md`):
|
|
6
6
|
the shipping dialect this pipeline emits (Layer A, `packages/gen-ui/a2ui/`) and
|
|
7
|
-
the
|
|
8
|
-
through `packages/genui/wire-bridge/`.
|
|
7
|
+
the A2UI v1.0 Candidate stack (Layer B, `packages/genui/`) reached
|
|
8
|
+
through `packages/genui/wire-bridge/`. **[amended 2026-08-29, ADR-0096]**
|
|
9
|
+
`adiahealth/gen-ui-system` is absorbed first-party under `packages/genui/`
|
|
10
|
+
and the standalone repo is archived — Layer B is in-repo source, not a
|
|
11
|
+
vendored dependency; `VENDOR.json` and the sync mechanism are gone (see
|
|
12
|
+
this same note on siblings `data-model-reactivity.md` and
|
|
13
|
+
`surface-lifecycle.md`). Candidate terminology is
|
|
9
14
|
**renderer/agent** — never client/server: `callableFrom` values are
|
|
10
15
|
`rendererOnly`/`agentOnly`/`rendererOrAgent`; the wire function kinds are
|
|
11
16
|
`callRendererFunction`/`callAgentFunction` +
|
|
12
17
|
`rendererFunctionResponse`/`agentFunctionResponse`; the MIME type is
|
|
13
18
|
`application/a2ui+json`; catalog resolution is strict (component `catalogId` →
|
|
14
19
|
surface `catalogId` → error, no registry default). The producer's
|
|
15
|
-
`wireFormat
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
20
|
+
`wireFormat` flag (`packages/genui/adia-producer/exit-gate.js`) **[flipped
|
|
21
|
+
2026-08-30, PR #2412]** now defaults to `'v1'`, not `'dialect'` — ADR-0072
|
|
22
|
+
ratified the flip (its precondition, genui-system#51's re-verification,
|
|
23
|
+
re-scoped and satisfied via ADR-0096/PR #2401) and the flip itself has since
|
|
24
|
+
executed. Documents authored here stay dialect-shaped; the bridge owns the
|
|
25
|
+
translation — never hand-write Candidate envelopes from this skill's
|
|
26
|
+
surfaces.
|
|
27
|
+
|
|
28
|
+
**Catalogs are opt-out scopes, not a taxonomy.** A2UI v1.0 lets a renderer
|
|
29
|
+
mix catalogs within one surface: `createSurface.catalogId` is the default,
|
|
30
|
+
and any component may carry its own `catalogId` to override it
|
|
31
|
+
(a2ui.org/specification/v1.0-a2ui/, "Catalog Reference"). So the useful way
|
|
32
|
+
to split a catalog is by what an app OMITS, never by component kind — Buttons
|
|
33
|
+
and Inputs are never omitted together, whole feature areas are. This is the
|
|
34
|
+
ratified basis of the five-catalog partition (gh#2211, Kim 2026-08-28):
|
|
35
|
+
`adia.core` / `adia.navigation` / `adia.data` / `adia.agent` / `adia.shells`,
|
|
36
|
+
every one a derived view over the yaml `category` axis and package path,
|
|
37
|
+
never a hand list. Two facts that trip a re-derivation: the `category` axis
|
|
38
|
+
had no `data` bucket for tables/charts (they declared `agent`; the partition's
|
|
39
|
+
pre-step re-categorizes five components), and `packages/web-modules/**` is a
|
|
40
|
+
path seam, not a category (its sidecars mostly declare `layout`/`container`).
|
|
41
|
+
The partition lives on the Layer B side only; the dialect catalog stays one
|
|
42
|
+
file per ADR-0059 §1. The 44 L1 harvested widgets are a pattern library, not
|
|
43
|
+
catalog members.
|
|
44
|
+
|
|
45
|
+
site-a2ui (the build-time HTML→A2UI docs-site transpile) was RULED fit as
|
|
46
|
+
the **dialect side's regression corpus**, not a v1.0 conformance bed
|
|
47
|
+
(ADR-0068): it exercised the dialect renderer, the ADR-0061 lifecycle path,
|
|
48
|
+
and the engine transpiler at real-content scale in production, but never
|
|
49
|
+
touched the producer, the bridge, or the `wireFormat` flag. That fitness
|
|
50
|
+
ruling was load-bearing on `wireFormat` defaulting to `'dialect'`, with a
|
|
51
|
+
**Named expiry**: the flag-flip ADR that makes `'v1'` the shipping default
|
|
52
|
+
had to re-rule site-a2ui's fitness (re-point vs retirement-by-attrition).
|
|
53
|
+
|
|
54
|
+
**[resolved, ADR-0072 — retired 2026-08-31]** That trigger fired and was
|
|
55
|
+
ruled: retirement, not re-point (Decision 2). Passive attrition never
|
|
56
|
+
netted the promoted set down (355 pages and growing at last measurement —
|
|
57
|
+
regen work kept adding rows faster than breakage retired them), so the
|
|
58
|
+
operator converted it to an active drain (gh#2410): the mechanism —
|
|
59
|
+
builder, ledger, artifact tree, gates, hook — is gone. The docs site now
|
|
60
|
+
renders every route through the legacy template path only; site-a2ui is
|
|
61
|
+
no longer part of this pipeline. The dialect side's regression-corpus
|
|
62
|
+
coverage site-a2ui once provided (real-content-scale exercise of the
|
|
63
|
+
dialect renderer, the ADR-0061 lifecycle path, and the engine transpiler)
|
|
64
|
+
has no standing replacement — see ADR-0072 Decision 2 / gh#2410 for the
|
|
65
|
+
closure record.
|
|
35
66
|
|
|
36
67
|
All paths repo-relative. Specs worth reading before structural changes:
|
|
37
68
|
`.claude/docs/specs/a2ui-v0.9-catalog-guide.md` (protocol + catalog format),
|
|
@@ -151,6 +182,11 @@ for any constant or decision lives in git and PR descriptions
|
|
|
151
182
|
currently fails, mostly a schema-generation gap around `data-*`/`span`
|
|
152
183
|
attributes, not corpus-content defects — see the ticket before
|
|
153
184
|
assuming a chunk is actually broken).
|
|
185
|
+
13. **Every generated catalog schema carries three synthesized universal
|
|
186
|
+
props today** (`slot`/`hidden`/`ariaLive`, none declared in any yaml
|
|
187
|
+
SoT); ADR-0097 rules a fourth, `traits`, but that part is decided-not-
|
|
188
|
+
yet-shipped (gh#2513) — see `primitive-authoring/references/
|
|
189
|
+
yaml-contract.md` §Synthesized universal props for the full contract.
|
|
154
190
|
|
|
155
191
|
## Test + run commands (all verified in root package.json)
|
|
156
192
|
|
|
@@ -57,13 +57,20 @@ Staleness is exposed as one attribute plus three events, never styling:
|
|
|
57
57
|
|
|
58
58
|
The lifecycle is renderer-runtime work on gen-ui-kit's side of the ADR-0059
|
|
59
59
|
line. The three wire envelope kinds (`beginSurfaceUpdate` /
|
|
60
|
-
`commitSurfaceUpdate` / `abortSurfaceUpdate` as v1.0 server kinds) are
|
|
61
|
-
|
|
62
|
-
gains nothing, the dialect wire format is byte-identical
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
`commitSurfaceUpdate` / `abortSurfaceUpdate` as v1.0 server kinds) are
|
|
61
|
+
defined by the v1.0 Candidate reference implementation — `a2ui.schema.json`
|
|
62
|
+
gains nothing, the dialect wire format is byte-identical. Never hand-write
|
|
63
|
+
lifecycle envelopes from this skill's surfaces; the Bridge owns the mapping
|
|
64
|
+
when the kinds land.
|
|
65
|
+
|
|
66
|
+
**[amended 2026-08-29, ADR-0096]** `adiahealth/gen-ui-system` is absorbed
|
|
67
|
+
first-party under `packages/genui/` and the standalone repo is archived —
|
|
68
|
+
there is no external, separately-governed "upstream standard" to wait on
|
|
69
|
+
any more. ADR-0096 Decision 5: gen-ui-kit "owns the whole stack now... and
|
|
70
|
+
the v1.0 Candidate reference implementation itself, not only the consumer
|
|
71
|
+
side of a vendor boundary." Stream-driven regeneration now proceeds on
|
|
72
|
+
gen-ui-kit's own schedule against the in-repo `packages/genui/` source,
|
|
73
|
+
not an upstream release.
|
|
67
74
|
|
|
68
75
|
## Interlock worth knowing
|
|
69
76
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: component-md-authoring
|
|
3
|
+
description: >-
|
|
4
|
+
Author the two judgment sections of a component's `component.md` —
|
|
5
|
+
Screen-reader spec and Behavioral spec — and keep it PR-fresh. Use when a
|
|
6
|
+
component's states, composed children, aria behavior, or error/empty/
|
|
7
|
+
loading handling changes and it already has (or should grow) a
|
|
8
|
+
`component.md`, or when asked to "add component.md for X" / "write the
|
|
9
|
+
screen-reader spec for X" / "why did check:component-md-fresh fail". NOT
|
|
10
|
+
the yaml prop/slot/event/token contract itself (primitive-authoring owns
|
|
11
|
+
that — this skill only owns the two authored yaml fields,
|
|
12
|
+
`screenReader`/`behavioral`, plus the optional `intent` field); NOT gen-ui
|
|
13
|
+
corpus/retrieval wiring (a2ui-maintenance); NOT a component's CSS token
|
|
14
|
+
audit (component-token-audit).
|
|
15
|
+
disable-model-invocation: false
|
|
16
|
+
user-invocable: true
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# component-md-authoring
|
|
20
|
+
|
|
21
|
+
`component.md` (gh#2615) is a generated shell, per-component, sitting next
|
|
22
|
+
to its `.yaml` SoT (`packages/web-components/components/<name>/component.md`,
|
|
23
|
+
or `packages/web-modules/<cluster>/<name>/component.md` for a composite/
|
|
24
|
+
shell). Every section except two is mechanically transcluded from the yaml
|
|
25
|
+
by `scripts/build/gen-component-md.mjs` — Intent, API (props/events/slots),
|
|
26
|
+
Structural (Light DOM anatomy + states + composes), Tokens, Rules,
|
|
27
|
+
Anti-patterns, Related. This skill's whole charter is the two sections that
|
|
28
|
+
aren't: **Screen-reader spec** and **Behavioral spec**.
|
|
29
|
+
|
|
30
|
+
## The load-bearing decision: where the authoring happens
|
|
31
|
+
|
|
32
|
+
You do not hand-edit `component.md`. You edit the yaml's `screenReader:`
|
|
33
|
+
and `behavioral:` fields (and, ideally, `intent:`) — `component.md` is
|
|
34
|
+
regenerated from them. This is deliberate, not incidental:
|
|
35
|
+
|
|
36
|
+
- **No second source of truth.** plan-2615's evidence pass on gh#2615 found
|
|
37
|
+
most of component.md's "intent layer" already lives in the yaml
|
|
38
|
+
(`a2ui.rules`, `anti_patterns`, `related`, examples). The two genuine
|
|
39
|
+
gaps — screen-reader and behavioral judgment — get the SAME treatment:
|
|
40
|
+
authored once, in yaml, transcluded everywhere else (component.md today;
|
|
41
|
+
gen-ui corpus derivation once a2ui-maintenance wires it in).
|
|
42
|
+
- **The freshness gate is PR-blocking, not staleness-only.** Because the
|
|
43
|
+
authored content lives in a yaml field, `component.md` is 100%
|
|
44
|
+
mechanically regenerable — `check:component-md-fresh` can do a real byte-
|
|
45
|
+
diff, the same shape as `check:reference-docs-fresh`, except PR-blocking
|
|
46
|
+
(operator ruling 2026-08-31) rather than advisory-only. A hand-edit
|
|
47
|
+
directly in `component.md` will be silently clobbered by the next
|
|
48
|
+
`npm run docs:component-md` and will fail the gate as "not fresh" — this
|
|
49
|
+
is the guard rail, not a bug.
|
|
50
|
+
|
|
51
|
+
## Authoring a component's two sections
|
|
52
|
+
|
|
53
|
+
1. Confirm the component doesn't already have adequate coverage — read its
|
|
54
|
+
existing `states:`, `a2ui.rules`, and `.class.js` source. Per
|
|
55
|
+
primitive-authoring's own first principle, **source wins**: verify every
|
|
56
|
+
claim you're about to write (focus order, aria attribute names, event
|
|
57
|
+
names) against the actual `.class.js`/`.js` file, not just the yaml
|
|
58
|
+
prose.
|
|
59
|
+
2. **Screen-reader spec** — focus order across composed children (order
|
|
60
|
+
`showModal()`/connect moves focus, what wraps at the tab boundary),
|
|
61
|
+
live-region announcement sequence (what fires `role="alert"` or an
|
|
62
|
+
`aria-live` region, and when), and any keyboard map beyond the trait
|
|
63
|
+
default (`pressable`/`focusable` already cover Enter/Space/click — only
|
|
64
|
+
document what's ADDITIONAL, e.g. arrow-key grid nav, Escape-dismiss).
|
|
65
|
+
Do not restate a static `aria-*` attribute the yaml's `props`/`states`
|
|
66
|
+
already document plainly — that's derived content, not new judgment.
|
|
67
|
+
3. **Behavioral spec** — dismiss/error/empty/loading states and
|
|
68
|
+
transitions NOT already modeled by `states:`. Distinguish "fetching" vs
|
|
69
|
+
"confirmed empty" where both exist (see `table.yaml`'s `screenReader`/
|
|
70
|
+
`behavioral` for a worked example: three distinct states, not one).
|
|
71
|
+
Name what is explicitly NOT handled (no built-in error state, no
|
|
72
|
+
built-in loading state) as clearly as what is — an absence is often the
|
|
73
|
+
more actionable fact for a consumer.
|
|
74
|
+
4. Both fields require `minLength: 20` (schema-enforced) — a placeholder
|
|
75
|
+
one-liner will fail `check:components-valid`. Write real prose, grounded
|
|
76
|
+
in source, not a restatement of the component's `description`.
|
|
77
|
+
5. Regenerate and verify:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
node scripts/build/components.mjs --validate # schema-valid yaml
|
|
81
|
+
npm run docs:component-md # regenerate component.md
|
|
82
|
+
npm run check:component-md-fresh # PR-blocking gate
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
6. If this is the component's FIRST component.md (yaml previously had
|
|
86
|
+
neither field), run `npm run build:components` too — the corpus/catalog
|
|
87
|
+
rebuild picks up the new yaml content, and `npm run eval:diff --
|
|
88
|
+
--engine zettel` should show no regression (preserve-not-regress floor,
|
|
89
|
+
owned by a2ui-maintenance).
|
|
90
|
+
|
|
91
|
+
## Scope (gh#2615 pilot)
|
|
92
|
+
|
|
93
|
+
`scripts/build/gen-component-md.mjs`'s `SCAN_ROOTS` covers
|
|
94
|
+
`packages/web-components/components/` and `packages/web-modules/chat/`
|
|
95
|
+
today — the pilot 5 (`button`, `modal`, `table`, `field`, `chat-shell`).
|
|
96
|
+
Extending to every web-modules cluster, or sweeping the remaining ~145
|
|
97
|
+
primitives, is deliberately out of scope for this pass (file a follow-up
|
|
98
|
+
task rather than silently expanding `SCAN_ROOTS` for one-off need — a
|
|
99
|
+
cluster added there without a plan for authoring every component inside it
|
|
100
|
+
just produces components with a `.yaml` but no eligible `component.md`,
|
|
101
|
+
which the generator already handles gracefully by skipping them, but which
|
|
102
|
+
defeats the point of a rollout plan).
|
|
103
|
+
|
|
104
|
+
## Cross-references
|
|
105
|
+
|
|
106
|
+
- `primitive-authoring`'s authoring-cycle: add "new/changed states or aria
|
|
107
|
+
behavior → component-md-authoring's authored sections may need a pass"
|
|
108
|
+
to your own SoT-change checklist when editing a yaml that already has a
|
|
109
|
+
`component.md` sibling.
|
|
110
|
+
- `scripts/schemas/component.yaml.schema.json` — `intent`/`screenReader`/
|
|
111
|
+
`behavioral` field definitions (all optional; a component with a `.yaml`
|
|
112
|
+
but neither authored field simply has no `component.md` yet).
|
|
113
|
+
- `scripts/verify/check-component-md-fresh.mjs` — the PR-blocking gate:
|
|
114
|
+
byte-freshness (component.md matches a fresh render) AND same-PR
|
|
115
|
+
coverage (a component.md-bearing component's source/yaml change must
|
|
116
|
+
touch component.md in the same diff).
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# Mode 1 — Component visual probe: probe classes + triage
|
|
2
2
|
|
|
3
|
+
`<plugin-root>` below is `$CLAUDE_PLUGIN_ROOT` in Claude Code; the plugin's installed directory
|
|
4
|
+
in Codex.
|
|
5
|
+
|
|
3
6
|
Detection layers, deepest last:
|
|
4
7
|
|
|
5
8
|
1. `npm run dogfood:visual-probe` — baseline per-page bar: no 4xx/5xx, no
|
|
6
9
|
console JS errors, non-zero body rect, ≥1 upgraded custom-element host.
|
|
7
10
|
Artifacts land in `qa/findings/visual-probe-<DATE>/`.
|
|
8
11
|
2. Bundled deep analyzer —
|
|
9
|
-
`node "
|
|
12
|
+
`node "<plugin-root>/skills/demo-audit/scripts/analyze.mjs"`.
|
|
10
13
|
Resolves the target checkout from `$ADIA_REPO_ROOT`, else cwd — always run
|
|
11
14
|
from (or point it at) the monorepo checkout, never the plugin install dir.
|
|
12
15
|
Flags: `--filter <slug>` · `--port N` (default 5173) · `--out PATH`
|
|
@@ -77,7 +77,10 @@ only that file, never the raw DOM/canvas/gallery-latest.json.** The
|
|
|
77
77
|
structural prompt-injection defense. Data model:
|
|
78
78
|
[loop-protocol](references/loop-protocol.md).
|
|
79
79
|
|
|
80
|
-
## Scripts (`node
|
|
80
|
+
## Scripts (`node <plugin-root>/skills/gen-ui-review/scripts/…`, from monorepo root)
|
|
81
|
+
|
|
82
|
+
`<plugin-root>` is `$CLAUDE_PLUGIN_ROOT` in Claude Code; the plugin's installed directory in
|
|
83
|
+
Codex.
|
|
81
84
|
|
|
82
85
|
| Command | Purpose |
|
|
83
86
|
| --- | --- |
|