@adia-ai/adia-ui-forge 0.8.42 → 0.8.44
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 +2 -2
- package/CHANGELOG.md +20 -0
- package/README.md +1 -1
- package/agents/a2ui-maintenance-agent.md +8 -10
- package/agents/a2ui-planner-agent.md +9 -11
- package/agents/{framework-alignment-agent.md → framework-checker.md} +10 -12
- package/agents/package-release-agent.md +8 -11
- package/agents/primitive-authoring-agent.md +6 -8
- package/package.json +1 -1
- package/skills/a2ui-maintenance/SKILL.md +49 -100
- package/skills/a2ui-maintenance/references/eval-diagnostics.md +18 -0
- package/skills/a2ui-maintenance/references/pipeline-overview.md +46 -0
- package/skills/demo-audit/SKILL.md +55 -120
- package/skills/demo-audit/references/auto-fix-allowlist.md +26 -0
- package/skills/demo-audit/references/mode7-status-battery.md +22 -0
- package/skills/demo-audit/references/output-contract-worked-example.md +24 -0
- package/skills/demo-audit/references/probe-discipline-and-escalation.md +27 -0
- package/skills/gen-ui-review/SKILL.md +68 -136
- package/skills/gen-ui-review/references/corpus-html-patterns.md +3 -3
- package/skills/gen-ui-review/references/exit-gate-mechanics.md +26 -0
- package/skills/gen-ui-review/references/lookup-maintenance.md +8 -0
- package/skills/gen-ui-review/references/loop-protocol.md +16 -0
- package/skills/gen-ui-review/references/scorecard-worked-examples.md +36 -0
- package/skills/gen-ui-review/scripts/gen-review-decompose.mjs +1 -1
- package/skills/package-release/SKILL.md +68 -56
- package/skills/package-release/references/authorization-model.md +34 -0
- package/skills/package-release/references/invariants-detail.md +61 -0
- package/skills/package-release/references/mechanization.md +54 -0
- package/skills/package-release/references/recovery-paths.md +10 -0
- package/skills/primitive-authoring/SKILL.md +13 -32
- package/skills/primitive-authoring/references/api-contract.md +58 -0
- package/skills/primitive-authoring/references/code-style.md +38 -0
- package/skills/primitive-authoring/references/primitive-audit.md +1 -1
- package/skills/primitive-authoring/references/svg-authoring.md +4 -4
- package/skills/primitive-authoring/references/token-contract.md +66 -6
- package/skills/site-deployment/SKILL.md +30 -88
- package/skills/site-deployment/references/deploy-playbooks.md +38 -0
- package/skills/ssr-compatibility/SKILL.md +28 -70
- package/skills/ssr-compatibility/references/failure-shapes.md +23 -0
|
@@ -22,6 +22,26 @@ static properties = {
|
|
|
22
22
|
- `reflect` — when `true`, JS property changes write back to the HTML attribute so CSS can match it. Required for every state-bearing Boolean. Usually safe to omit for large value props (long strings, big objects).
|
|
23
23
|
- `attribute` — explicit kebab-case mapping when the JS name doesn't auto-convert cleanly. `camelCase` → `camel-case` automatic; override via `attribute: 'max-length'` when you want non-default behavior.
|
|
24
24
|
|
|
25
|
+
[verified 2026-08-19] **Literal-string `"false"` parity (ADR-0075).** For a
|
|
26
|
+
`Boolean`-typed prop, `parseAttr` (`core/element.js:93-94`) special-cases the
|
|
27
|
+
literal attribute string `"false"` to parse as JS `false` — a deliberate
|
|
28
|
+
deviation from strict HTML boolean-attribute semantics (where any presence,
|
|
29
|
+
including `attr="false"`, means `true`). This matches the A2UI transpiler's
|
|
30
|
+
own prior `// (defensive)` special-case
|
|
31
|
+
(`compose/transpiler/transpiler-maps.js:206-207`), so identical markup now
|
|
32
|
+
parses the same in the live DOM and in transpiled/generated output. Quoting
|
|
33
|
+
the Decision: "for `t === Boolean`, the literal string `\"false\"` parses to
|
|
34
|
+
JS `false`; any other present value (including empty string) parses to
|
|
35
|
+
`true`; absence stays `false`." A component author or generation pipeline
|
|
36
|
+
that writes `interactive="false"` on a `default: true` Boolean prop gets the
|
|
37
|
+
intuitive result — do not assume strict HTML semantics here. (`default:
|
|
38
|
+
true` itself remains the rare, ratified exception this ADR's blast radius
|
|
39
|
+
runs against — ADR-0063's stamped-attribute mechanism, gh#961 — never the
|
|
40
|
+
default shape "Boolean prop naming — the flip rule" above documents.) Named
|
|
41
|
+
blast radius already shipped and relying on this: `password-strength-ui
|
|
42
|
+
show-label="false"`, `nav-group-ui collapsible="false"`
|
|
43
|
+
(`.examples.html` demos for both).
|
|
44
|
+
|
|
25
45
|
## The `attr:` silent-typo trap
|
|
26
46
|
|
|
27
47
|
A real bug caught in iteration 4:
|
|
@@ -61,6 +81,44 @@ AdiaUI conventions require `default: false` on all Booleans. The naming follows:
|
|
|
61
81
|
- `enabled` (invert to `disabled`), `visible` (invert to `hidden`) — these clash with standard HTML attribute vocabulary.
|
|
62
82
|
- Double-negatives like `unhide` or `dontSkip`.
|
|
63
83
|
|
|
84
|
+
## Enum attribute + container-query auto-snap default — no interpolation
|
|
85
|
+
|
|
86
|
+
[verified 2026-08-19] ADR-0074 establishes the cross-component pattern for a
|
|
87
|
+
value-enum attribute whose sensible default is "pick automatically from live
|
|
88
|
+
layout, but let a consumer pin it explicitly": `chart-ui` /
|
|
89
|
+
`chart-legend-ui` / `chart-in-card`'s `ratio` attribute, three allowed
|
|
90
|
+
values (`3:2`, `1:1`, `2:3`), unset by default. Quoting the Decision:
|
|
91
|
+
"Unset (the default, empty string) means auto-snap: a CSS container query on
|
|
92
|
+
the element's own box compares its live aspect ratio against two midpoint
|
|
93
|
+
boundaries … and renders the nearest named ratio's studied layout — never a
|
|
94
|
+
scaled/interpolated blend. Setting `ratio` explicitly pins that ratio's
|
|
95
|
+
rendering regardless of the container's actual aspect, overriding the
|
|
96
|
+
container query."
|
|
97
|
+
|
|
98
|
+
Shape to follow for a new attribute of this kind:
|
|
99
|
+
|
|
100
|
+
- A plain value-enum attribute, not a boolean — ADR-0063's `no-*`
|
|
101
|
+
negation-prefix grammar does not apply.
|
|
102
|
+
- Unset/default means container-query-driven auto-snap against studied
|
|
103
|
+
breakpoints — discrete snapping between named values, never continuous
|
|
104
|
+
interpolation (an in-between state was never individually studied, so it
|
|
105
|
+
can't carry a "deliberately designed" claim).
|
|
106
|
+
- An explicit attribute value always pins and overrides the auto-snap,
|
|
107
|
+
never blends with it.
|
|
108
|
+
- The resolved value reflects back for CSS/consumer introspection —
|
|
109
|
+
`data-ratio-resolved` on `chart-ui` names which of the enum's snapped or
|
|
110
|
+
pinned values is currently in effect, distinct from the (possibly unset)
|
|
111
|
+
`ratio` attribute itself.
|
|
112
|
+
- Don't fold the new axis into an existing enum attribute that covers a
|
|
113
|
+
different concern (`size` stayed `sm|md|lg`-only; `ratio` didn't grow a
|
|
114
|
+
compound value like `lg-2:3`) — orthogonal axes get their own attribute.
|
|
115
|
+
|
|
116
|
+
`table-toolbar-ui[stage]` (ADR-0076) is the second precedent for this same
|
|
117
|
+
shape — see the ADR-0063 conventions list in
|
|
118
|
+
[token-contract.md](token-contract.md).
|
|
119
|
+
|
|
120
|
+
Source: [ADR-0074](../../../../../../docs/ops/adr/adr-0074-chart-ratio-attribute-grammar.md).
|
|
121
|
+
|
|
64
122
|
## Numeric props — `null` over sentinels
|
|
65
123
|
|
|
66
124
|
Indeterminate, unknown, or "not yet set" numeric state uses `null`, not `-1` or `Infinity`:
|
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# AdiaUI code style — best practices
|
|
2
2
|
|
|
3
|
+
## First principles (SKILL.md's own summary, expanded)
|
|
4
|
+
|
|
5
|
+
1. **Invariants are enforced by the next author, not the linter.** A component
|
|
6
|
+
that violates the contract silently teaches the next agent the violation is
|
|
7
|
+
acceptable. Write as if your component is the reference the next one is
|
|
8
|
+
patterned after — because it will be.
|
|
9
|
+
2. **Default behavior is the absent attribute.** `<component-ui>` with no
|
|
10
|
+
attributes does the expected default thing; every Boolean prop defaults to
|
|
11
|
+
`false`. If the expected default is "on," the prop name is wrong — flip it
|
|
12
|
+
(`closable` → `permanent`, `animate` → `static`).
|
|
13
|
+
3. **Variants change tokens; modes change layout.** A variant body contains
|
|
14
|
+
only `--component-*: var(...)` lines — no `padding`, `display`, `position`,
|
|
15
|
+
`width`, `height`, `gap`, `flex`, `grid`, `overflow`, `border-radius`.
|
|
16
|
+
Layout-changing attributes are modes and require a Sanctioned Mode
|
|
17
|
+
Attributes entry in the contract doc.
|
|
18
|
+
4. **Symmetric lifecycle or it's a leak.** Every listener added in
|
|
19
|
+
`connected()` is removed in `disconnected()`; every timer cleared, observer
|
|
20
|
+
disconnected, cached ref nulled. Handlers are stable `#field` arrows so
|
|
21
|
+
`removeEventListener` can match — inline arrows bit three components in one
|
|
22
|
+
audit cycle.
|
|
23
|
+
5. **Component tokens consume L3, not L2.** Alias from the role×state matrix
|
|
24
|
+
(`--a-primary-bg-hover`), never the family base (`--a-primary`) — bypassing
|
|
25
|
+
L3 strands the component outside theme / dark-mode / contrast cascades
|
|
26
|
+
silently.
|
|
27
|
+
|
|
3
28
|
Modern AdiaUI is small, declarative, and token-driven. Most of the "bugs" agents write are bugs _against the conventions_ — bare `<div>`s where `col-ui` belongs, raw `<input>` where `input-ui` belongs, hex colors where tokens belong. The conventions are not stylistic preferences; each one corresponds to a working feature (theme switching, density modes, form association, focus rings) that breaks silently when the convention is violated.
|
|
4
29
|
|
|
5
30
|
When in doubt: look up the catalog (the `lookup_component` MCP tool, or the adia-factory plugin's composition skill), pick the existing primitive, wire through tokens.
|
|
@@ -221,6 +246,19 @@ Default behavior is the absent attribute. Attributes exist to opt OUT or carry a
|
|
|
221
246
|
|
|
222
247
|
State-bearing Booleans must `reflect: true` so CSS can match `:scope[disabled]`, `:scope[selected]`, etc. Without reflection, hover / active / selected styles break silently.
|
|
223
248
|
|
|
249
|
+
[verified 2026-08-19] **The literal string `"false"` is not "presence = true"
|
|
250
|
+
here.** ADR-0075 special-cases `parseAttr` for `Boolean`-typed props so the
|
|
251
|
+
literal attribute string `"false"` parses to JS `false`, not `true` —
|
|
252
|
+
deviating from strict HTML semantics on purpose, to match this repo's own
|
|
253
|
+
A2UI transpiler (which already special-cased it defensively) and the
|
|
254
|
+
authoring intuition both human and generated markup default to. Writing
|
|
255
|
+
`show-label="false"` on a `default: true` Boolean prop does what it looks
|
|
256
|
+
like it does. (`default: true` is itself the rare, ratified exception to
|
|
257
|
+
this section's "every Boolean defaults to `false`" rule — ADR-0063's
|
|
258
|
+
stamped-attribute mechanism, gh#961 — not a license to skip the flip rule
|
|
259
|
+
above for a new prop.) See [api-contract.md](api-contract.md)'s field-rules
|
|
260
|
+
section for the full rule and shipped blast radius (`core/element.js:93-94`).
|
|
261
|
+
|
|
224
262
|
Native DOM accessors (`textContent`, `innerHTML`) get clobbered if declared in `static properties` — `installProps` overrides the native setter and `el.textContent = ''` becomes a signal write, not a child-wipe. Don't declare those names in `static properties`.
|
|
225
263
|
|
|
226
264
|
## Symmetric lifecycle
|
|
@@ -66,7 +66,7 @@ Treat this as the default lookup table. **If your affordance maps to a row here,
|
|
|
66
66
|
| Modal / drawer / toast | `modal-ui` / `drawer-ui` / `toast-ui` |
|
|
67
67
|
| Switch / checkbox / radio / segmented / toggle group | `switch-ui` / `check-ui` / `radio-ui` / `segmented-ui` / `toggle-group-ui` |
|
|
68
68
|
| Avatar / avatar group | `avatar-ui` / `avatar-group-ui` |
|
|
69
|
-
| Color / calendar / OTP picker | `color-
|
|
69
|
+
| Color / calendar / OTP picker | `color-area-ui` / `calendar-picker-ui` / `otp-input-ui` |
|
|
70
70
|
| Stepper / timeline / progress / progress-row | `stepper-ui` / `timeline-ui` / `progress-ui` / `progress-row-ui` |
|
|
71
71
|
| Card with header / section / footer | `card-ui` (+ slot children) — don't roll a "panel" |
|
|
72
72
|
| Description-list (key/value) | `description-list-ui` |
|
|
@@ -90,9 +90,9 @@ chart's Y-axis labels clipped under a bled card's corner.
|
|
|
90
90
|
```css
|
|
91
91
|
/* Guide/value/legend safety inset (gh#1095) — [bleed]'s zero-inset is
|
|
92
92
|
only collision-free for the bare-marks case: a sparkline, or any other
|
|
93
|
-
chart-ui type with BOTH [
|
|
94
|
-
& > section[bleed]:has(> chart-ui:not([type="sparkline"]):not([
|
|
95
|
-
& > section[bleed]:has(> chart-ui:not([type="sparkline"]):not([
|
|
93
|
+
chart-ui type with BOTH [no-grid] and [no-values] set. */
|
|
94
|
+
& > section[bleed]:has(> chart-ui:not([type="sparkline"]):not([no-grid])),
|
|
95
|
+
& > section[bleed]:has(> chart-ui:not([type="sparkline"]):not([no-values])),
|
|
96
96
|
& > section[bleed]:has(> chart-legend-ui) {
|
|
97
97
|
margin: var(--card-inset);
|
|
98
98
|
padding: 0;
|
|
@@ -105,7 +105,7 @@ relying on every author to remember. `#renderSparkline()` is the ONLY
|
|
|
105
105
|
renderer that never emits axis ticks, gridlines, value text, or a legend
|
|
106
106
|
(`chart.class.js`'s sparkline branch) — it's the sole type where genuine
|
|
107
107
|
edge-to-edge bleed is collision-free with no attribute needed. Every other
|
|
108
|
-
type needs BOTH `[
|
|
108
|
+
type needs BOTH `[no-grid]` and `[no-values]` set to opt into true
|
|
109
109
|
bleed; short of that, the safety inset applies and the chart renders inset
|
|
110
110
|
like a normal card section — expected, not a bug.
|
|
111
111
|
|
|
@@ -142,7 +142,7 @@ mechanics; never work from this summary alone):
|
|
|
142
142
|
granted since: `qr-code-ui[color]` / `icon-ui[weight]`, ADR-0070).
|
|
143
143
|
|
|
144
144
|
Beyond the global grammar, ADR-0063
|
|
145
|
-
(`docs/ops/adr/adr-0063-attribute-grammar-addendum.md`) ratifies
|
|
145
|
+
(`docs/ops/adr/adr-0063-attribute-grammar-addendum.md`) ratifies seven
|
|
146
146
|
CROSS-SIBLING conventions for component-local attribute naming — the axis
|
|
147
147
|
ADR-0053/0054 don't cover. Any new attribute follows these:
|
|
148
148
|
|
|
@@ -179,11 +179,71 @@ ADR-0053/0054 don't cover. Any new attribute follows these:
|
|
|
179
179
|
- **`-picker` is reserved for the outer trigger+popover form-associated
|
|
180
180
|
composite** — never the inline substrate it composes. `color-picker-ui`
|
|
181
181
|
(the inline substrate) renames to `color-area-ui`.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
182
|
+
- **[verified 2026-08-19] Preset-boolean-vs-alias-retirement boundary**
|
|
183
|
+
(ADR-0076): a boolean attribute that is additive sugar over several
|
|
184
|
+
existing granular `no-*` opt-outs — `table-toolbar-ui[chrome-only]`
|
|
185
|
+
equivalent to setting all four of `no-filter`, `no-sort`, `no-columns`,
|
|
186
|
+
`no-search` — is NOT an ADR-0063-style alias-retirement case (the
|
|
187
|
+
granular attributes aren't duplicate spellings of one concept the way
|
|
188
|
+
`alert[dismissible]`/`[closable]` were; a consumer may legitimately want
|
|
189
|
+
a subset off, a combination the preset alone can't express), so the
|
|
190
|
+
granular attributes stay shipped, independently-addressable API with no
|
|
191
|
+
deprecation. The preset's precedence is **pure, absolute OR — never a
|
|
192
|
+
tri-state**: while set, all covered controls are off, full stop, with no
|
|
193
|
+
partial re-enable via clearing an individual `no-*` while the preset
|
|
194
|
+
remains set (ADR-0076: "`no-*` attributes are presence-based booleans …
|
|
195
|
+
meaning 'presence forces off' is expressible but 'absence means defer to
|
|
196
|
+
[the preset]'s own OR' is not without a tri-state amendment"). A second
|
|
197
|
+
auto-snap-enum precedent alongside ADR-0074's chart `ratio` lands here
|
|
198
|
+
too: `table-toolbar-ui[stage]` (`full | search-tight | icon-only |
|
|
199
|
+
overflow`) — unset auto-snaps via `@container` queries against studied
|
|
200
|
+
breakpoints, an explicit value pins and overrides the query, no
|
|
201
|
+
interpolation between stages.
|
|
202
|
+
|
|
203
|
+
The renames above have LANDED: dual-read compat shims shipped via gh#1563,
|
|
204
|
+
and the breaking cut removed the old names in 0.8.43 (gh#1617). The new
|
|
205
|
+
spellings are the sole ones — a yaml or demo still showing an old name is
|
|
206
|
+
stale and should be fixed.
|
|
207
|
+
|
|
208
|
+
## Disabled-state tokens
|
|
209
|
+
|
|
210
|
+
[verified 2026-08-19] ADR-0073 ratifies the standing convention for any
|
|
211
|
+
control supporting the `disabled` boolean attribute/state:
|
|
212
|
+
|
|
213
|
+
- **Shared bg role, container-low tier.** `--a-ui-bg-disabled`
|
|
214
|
+
(`styles/colors/semantics/features.css`) resolves to
|
|
215
|
+
`var(--md-sys-color-neutral-container-low)` — the same 10%-tint role
|
|
216
|
+
`--a-bg-hover` / `--a-bg-muted` ride for REST-state de-emphasis. A
|
|
217
|
+
component's own `--<component>-bg-disabled` indirection aliases this
|
|
218
|
+
shared role — never a raw color, never a per-family `*-container-low`
|
|
219
|
+
variant (none exists in the disabled path).
|
|
220
|
+
- **`[state][disabled]` specificity override for checked/selected fills.**
|
|
221
|
+
A `[checked]`/`[selected]` selector outranks a plain `[disabled]` rule on
|
|
222
|
+
CSS specificity (2 attribute selectors beat 1), so a checked+disabled
|
|
223
|
+
control silently keeps its active-state fill unless the component adds an
|
|
224
|
+
explicit higher-specificity override — `[checked][disabled]` /
|
|
225
|
+
`[selected][disabled]` — routed through its own
|
|
226
|
+
`--<component>-...-checked-disabled` (or `-selected-disabled`) custom
|
|
227
|
+
prop, itself aliasing `--a-ui-bg-disabled`.
|
|
228
|
+
- **Reduced-contrast disabled border — the `--input-border-disabled`
|
|
229
|
+
pattern.** Any control that renders a border when disabled adds
|
|
230
|
+
`--<component>-border-disabled: var(--a-ui-border-disabled)`, applied as
|
|
231
|
+
`border-color` under `:scope[disabled]`.
|
|
232
|
+
|
|
233
|
+
Quoting ADR-0073's Decision 5: "a control supporting `disabled` uses the
|
|
234
|
+
shared, single neutral `--md-sys-color-neutral-container-low` role for
|
|
235
|
+
background … always through a `--<component>-bg-disabled` … indirection
|
|
236
|
+
aliasing `--a-ui-bg-disabled` … uses the reduced-contrast
|
|
237
|
+
`--input-border-disabled` pattern … for any border it renders when disabled
|
|
238
|
+
… overrides any checked/selected state fill at `[state][disabled]`
|
|
239
|
+
specificity rather than relying on `[disabled]` alone."
|
|
240
|
+
|
|
241
|
+
The re-runnable check is `scripts/audit/audit-disabled-fill-tokens.mjs`
|
|
242
|
+
(`check:disabled-fill-audit`, advisory) — it fails any `[disabled]`-scoped
|
|
243
|
+
`background`/`background-color` that doesn't resolve through a
|
|
244
|
+
disabled-aware indirection.
|
|
245
|
+
|
|
246
|
+
Source: [ADR-0073](../../../../../../docs/ops/adr/adr-0073-disabled-state-container-low-token-convention.md).
|
|
187
247
|
|
|
188
248
|
## When to update this reference
|
|
189
249
|
|
|
@@ -28,13 +28,11 @@ directives are findings.
|
|
|
28
28
|
|
|
29
29
|
## Platform contract (the non-obvious bits)
|
|
30
30
|
|
|
31
|
-
- The app binds **`:8000` plain HTTP**; the exe.dev edge terminates TLS
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
processes run as it. Preinstalled: `git`, `rsync`, `docker`. NOT
|
|
37
|
-
preinstalled: `caddy`, `node`, `nginx` (apt + NodeSource).
|
|
31
|
+
- The app binds **`:8000` plain HTTP**; the exe.dev edge terminates TLS and
|
|
32
|
+
forwards to VM `:8000`. Nothing listening ⇒ "**Port 8000 unbound.**"
|
|
33
|
+
Don't bind :443 on the VM.
|
|
34
|
+
- Default user **`exedev`** (uid 1000, `sudo` + `docker`); service runs as
|
|
35
|
+
it. Preinstalled: `git`, `rsync`, `docker`. NOT: `caddy`, `node`, `nginx`.
|
|
38
36
|
- **`127.0.0.1:9999` runs `shelley`** — exe.dev's internal agent,
|
|
39
37
|
localhost-only. Leave it running; don't bind 9999.
|
|
40
38
|
- Disk: 25 GB on `/`. New VMs ship RSA-2048-only host keys — verify the
|
|
@@ -47,18 +45,15 @@ Standard layout: `/srv/<app>/dist/` webroot (exedev-owned) ·
|
|
|
47
45
|
|
|
48
46
|
## Deploy-freshness cadence — a lockstep cut is not a site deploy
|
|
49
47
|
|
|
50
|
-
`package-release` cutting and publishing
|
|
51
|
-
`
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
and the deployed site sat a week behind npm before anyone noticed. When
|
|
60
|
-
handing off from a release, check whether the cut touched a served package
|
|
61
|
-
and close the loop before calling the cycle done.
|
|
48
|
+
`package-release` cutting and publishing does **not** itself update
|
|
49
|
+
`ui-kit.exe.xyz` — only a `site-v*` tag push does. Any lockstep cut that
|
|
50
|
+
changes a package the site actually serves (`web-components`,
|
|
51
|
+
`web-modules`, `llm`, `a2ui/*`) **owes a site deploy in the same release
|
|
52
|
+
cycle**, or an explicit, recorded operator decision to skip it. "The
|
|
53
|
+
release finished" is not evidence the site is current (the v0.8.x window:
|
|
54
|
+
a served-package cut landed with no matching tag, site sat a week stale).
|
|
55
|
+
When handing off from a release, check whether the cut touched a served
|
|
56
|
+
package before calling the cycle done.
|
|
62
57
|
|
|
63
58
|
## Current deployments
|
|
64
59
|
|
|
@@ -72,60 +67,22 @@ VM artifacts (Caddyfile, unit, env example) live in repo `deploy/`.
|
|
|
72
67
|
|
|
73
68
|
**Never run `npm run deploy:site` from a local shell.** Push a tag matching
|
|
74
69
|
`site-v*` (or run the workflow via `workflow_dispatch`) — `deploy-site.yml`
|
|
75
|
-
builds, dry-runs, and
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
`
|
|
83
|
-
|
|
84
|
-
destructive (a 2026-06-08 manual run deleted 3,572 files) — every step
|
|
85
|
-
below is incident-earned; the reference carries the full commands and the
|
|
86
|
-
delete-adjudication classes.
|
|
87
|
-
|
|
88
|
-
1. Build from clean, fully-merged `main`. In a fresh worktree,
|
|
89
|
-
`npm run build -w @adia-ai/llm` runs **before** `build:site` — llm compiles
|
|
90
|
-
at publish time and its outputs are gitignored, so without it
|
|
91
|
-
`/packages/llm/core/index.js` 404s and component registration breaks site-wide
|
|
92
|
-
(found live 2026-06-09).
|
|
93
|
-
2. Dry-run `rsync -azni --delete --exclude='packages/gen-ui/a2ui/corpus/feedback/'`
|
|
94
|
-
and bucket **every** `*deleting` line into a known-safe class; any
|
|
95
|
-
unexplained served-content delete aborts the deploy. The exclude protects
|
|
96
|
-
prod-only runtime-written files (feedback logs wiped 2026-06-10).
|
|
97
|
-
3. Snapshot prod: `cp -al /srv/<app>/dist /srv/<app>/dist.bak-<date>`
|
|
98
|
-
(hardlink farm — instant, and the only rollback).
|
|
99
|
-
4. Real rsync — the dry-run minus `-n`, SAME excludes, and the SAME BYTES:
|
|
100
|
-
both jobs rsync the artifact-round-tripped tree, and the deploy job aborts
|
|
101
|
-
if its tree's sha256 fingerprint differs from the one whose delete summary
|
|
102
|
-
was approved (gh#425 — the dry-run used to read the build job's own
|
|
103
|
-
`dist/`, so the v0.8.14 summary listed 3,128 deletions the real send never
|
|
104
|
-
performed; an over-reporting summary trains reviewers to approve past scary
|
|
105
|
-
numbers, and the same gap can under-report).
|
|
106
|
-
5. Verify a fixture **file** that only exists in the new build, then
|
|
107
|
-
render-check a `/site/components/*` page headlessly (SPA returns 200 shell
|
|
108
|
-
for any route; file presence alone misses the llm-404 class).
|
|
109
|
-
6. On verify failure: `rm -rf dist && mv dist.bak-<date> dist`. Keep the
|
|
110
|
-
snapshot until the deploy is confirmed good.
|
|
70
|
+
builds, dry-runs, and waits for a human to read the delete summary before
|
|
71
|
+
the destructive `deploy` job runs. `npm run deploy:site` still exists
|
|
72
|
+
locally for CI-unavailable fallback only, and is destructive (a 2026-06-08
|
|
73
|
+
manual run deleted 3,572 files). The full step-by-step hardened sequence
|
|
74
|
+
(llm-build-first gotcha, delete-adjudication classes, snapshot, verified
|
|
75
|
+
rsync, fixture+render verify, rollback) is in
|
|
76
|
+
[deploy-playbooks.md](references/deploy-playbooks.md)'s "Hardened
|
|
77
|
+
`--delete` deploy sequence" — every step there is incident-earned; read it
|
|
78
|
+
before running the fallback by hand.
|
|
111
79
|
|
|
112
80
|
If `server.js` changed: rsync it, then `sudo systemctl restart <app>`.
|
|
113
81
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
-
|
|
117
|
-
|
|
118
|
-
VM's `~exedev/.ssh/authorized_keys`; private half goes in
|
|
119
|
-
Settings → Secrets and variables → Actions, pasted directly — it should
|
|
120
|
-
never appear in an agent's Bash context or a commit.
|
|
121
|
-
- **Environment `production-site` reviewer gate — CONFIGURED** (verified
|
|
122
|
-
live 2026-08-11, `gh api repos/<org>/<repo>/environments`:
|
|
123
|
-
`protection_rules` carries `required_reviewers`). The `deploy` job in
|
|
124
|
-
`deploy-site.yml` therefore blocks on a human approval after its dry-run
|
|
125
|
-
job — the delete-adjudication gate this skill's hardened-deploy design
|
|
126
|
-
assumes. Changing the reviewer set is operator-only (repo Settings →
|
|
127
|
-
Environments → `production-site`) — no agent can configure it. Re-check
|
|
128
|
-
the API output before trusting this line; it drifts with repo settings.
|
|
82
|
+
One-time CI setup for `ui-kit.exe.xyz` (the deploy SSH key, the
|
|
83
|
+
`production-site` reviewer gate) is in
|
|
84
|
+
[deploy-playbooks.md](references/deploy-playbooks.md)'s own section by
|
|
85
|
+
that name.
|
|
129
86
|
|
|
130
87
|
## Other playbooks (reference §-anchors)
|
|
131
88
|
|
|
@@ -161,24 +118,9 @@ rollback state: not-needed | rolled-back — <if rolled back, what triggered
|
|
|
161
118
|
verdict: shipped | held — <one line>
|
|
162
119
|
```
|
|
163
120
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
Deploy Record
|
|
168
|
-
tag / run id: site-v4 (workflow run 29586391343, 2026-07-17T14:03:15Z)
|
|
169
|
-
dry-run deletes: see the run's dry-run job log for the class breakdown
|
|
170
|
-
fixture verified: pass — CI's post-deploy verify step, run marked success
|
|
171
|
-
render verified: pass — CI's post-deploy verify step, run marked success
|
|
172
|
-
snapshot: CI pre-deploy hardlink step (deploy-site.yml)
|
|
173
|
-
rollback state: not-needed
|
|
174
|
-
verdict: shipped
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
This example cites the run URL rather than restating its log inline — the
|
|
178
|
-
record's job is to point at the evidence, not transcribe it; re-derive the
|
|
179
|
-
dry-run/fixture/render lines from `gh run view <id> --log` if the detail is
|
|
180
|
-
ever needed, don't assume this filled example's prose stays current with a
|
|
181
|
-
run that already happened.
|
|
121
|
+
A filled worked example (a real cut) is in
|
|
122
|
+
[deploy-playbooks.md](references/deploy-playbooks.md)'s own "Deploy Record"
|
|
123
|
+
section.
|
|
182
124
|
|
|
183
125
|
## Hard gates
|
|
184
126
|
|
|
@@ -97,6 +97,22 @@ sudo vim /etc/<app>.env
|
|
|
97
97
|
sudo systemctl restart <app>
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
+
## One-time CI setup for `ui-kit.exe.xyz`
|
|
101
|
+
|
|
102
|
+
- **Repo secret `SITE_DEPLOY_SSH_KEY`** — done. An ed25519 keypair generated
|
|
103
|
+
*by a human*, never by the agent (Hard gate 1). Public half goes in the
|
|
104
|
+
VM's `~exedev/.ssh/authorized_keys`; private half goes in
|
|
105
|
+
Settings → Secrets and variables → Actions, pasted directly — it should
|
|
106
|
+
never appear in an agent's Bash context or a commit.
|
|
107
|
+
- **Environment `production-site` reviewer gate — CONFIGURED** (verified
|
|
108
|
+
live 2026-08-11, `gh api repos/<org>/<repo>/environments`:
|
|
109
|
+
`protection_rules` carries `required_reviewers`). The `deploy` job in
|
|
110
|
+
`deploy-site.yml` therefore blocks on a human approval after its dry-run
|
|
111
|
+
job — the delete-adjudication gate this skill's hardened-deploy design
|
|
112
|
+
assumes. Changing the reviewer set is operator-only (repo Settings →
|
|
113
|
+
Environments → `production-site`) — no agent can configure it. Re-check
|
|
114
|
+
the API output before trusting this line; it drifts with repo settings.
|
|
115
|
+
|
|
100
116
|
## Playbook: deploy an update
|
|
101
117
|
|
|
102
118
|
**Push a tag matching `site-v*`** (or trigger `.github/workflows/deploy-site.yml`
|
|
@@ -282,3 +298,25 @@ Add new hosts here as they come online.
|
|
|
282
298
|
|
|
283
299
|
Release engineering (`package-release`) builds and publishes artifacts; this
|
|
284
300
|
playbook owns the deploy step that pushes them to the VM.
|
|
301
|
+
|
|
302
|
+
## Deploy Record — a filled example
|
|
303
|
+
|
|
304
|
+
The schema lives in SKILL.md's own "The Deploy Record" section; this is a
|
|
305
|
+
worked example (a real cut, `gh run view 29586391343`):
|
|
306
|
+
|
|
307
|
+
```text
|
|
308
|
+
Deploy Record
|
|
309
|
+
tag / run id: site-v4 (workflow run 29586391343, 2026-07-17T14:03:15Z)
|
|
310
|
+
dry-run deletes: see the run's dry-run job log for the class breakdown
|
|
311
|
+
fixture verified: pass — CI's post-deploy verify step, run marked success
|
|
312
|
+
render verified: pass — CI's post-deploy verify step, run marked success
|
|
313
|
+
snapshot: CI pre-deploy hardlink step (deploy-site.yml)
|
|
314
|
+
rollback state: not-needed
|
|
315
|
+
verdict: shipped
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
This example cites the run URL rather than restating its log inline — the
|
|
319
|
+
record's job is to point at the evidence, not transcribe it; re-derive the
|
|
320
|
+
dry-run/fixture/render lines from `gh run view <id> --log` if the detail is
|
|
321
|
+
ever needed, don't assume this filled example's prose stays current with a
|
|
322
|
+
run that already happened.
|
|
@@ -2,17 +2,16 @@
|
|
|
2
2
|
name: ssr-compatibility
|
|
3
3
|
description: >-
|
|
4
4
|
Answers why an AdiaUI component crashes, drops content, or renders wrong
|
|
5
|
-
under SSR (linkedom/Astro
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
attachInternals/ResizeObserver/adoptedStyleSheets/matchMedia/`instanceof
|
|
9
|
-
under a DOM shim, why
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
host/hydration wiring (host-wiring, adia-ui-factory plugin).
|
|
5
|
+
under SSR (linkedom/Astro) — the four known failure shapes, what's fixed
|
|
6
|
+
vs open, how to prove a fix under the linkedom shim gate. Use for "does
|
|
7
|
+
this work under SSR", why a component crashes on
|
|
8
|
+
attachInternals/ResizeObserver/adoptedStyleSheets/matchMedia/`instanceof
|
|
9
|
+
Node` under a DOM shim, why table-ui/chart-ui/select-ui or a container CE
|
|
10
|
+
renders empty or drops nested children server-rendered, whether
|
|
11
|
+
getBoundingClientRect() is safe in connectedCallback, or whether a shim
|
|
12
|
+
can be deleted after a fix ships. ANSWERS only. NOT for a fix
|
|
13
|
+
(primitive-authoring) or host/hydration wiring (host-wiring,
|
|
14
|
+
adia-ui-factory).
|
|
16
15
|
disable-model-invocation: false
|
|
17
16
|
user-invocable: false
|
|
18
17
|
---
|
|
@@ -29,36 +28,14 @@ this" and "what's the state of each shape's fix" — it never carries the fix it
|
|
|
29
28
|
|
|
30
29
|
## The four shapes, in one line each
|
|
31
30
|
|
|
32
|
-
1. **A browser-only API is called unconditionally → crash
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
content, so this specific mechanism isn't exposed today; a forward audit catches
|
|
38
|
-
a future regression. **Do not stop here** — see shape 2b, the actual live bug
|
|
39
|
-
found investigating the same report.
|
|
40
|
-
2b. **Custom-element upgrade doesn't replay `attributeChangedCallback` for
|
|
41
|
-
pre-existing attributes → reflected properties stuck at their class default.**
|
|
42
|
-
The REAL mechanism behind gh#284's reported symptom. happy-dom and (by strong
|
|
43
|
-
inference) linkedom both skip the custom-elements spec's upgrade-time
|
|
44
|
-
attribute replay (§4.13.5 step 6); any `reflect: true` property seeded only
|
|
45
|
-
from pre-parsed/SSR HTML never initializes. **Fixed 2026-07-18** (gh#284,
|
|
46
|
-
PR #309) — `connectedCallback` now re-syncs every declared property from its
|
|
47
|
-
live attribute before `connected()` runs.
|
|
48
|
-
3. **A connect-time layout measurement is treated as confirmed, not unknown.** A zero
|
|
49
|
-
rect (shim, or real pre-layout connect) drives a wrong persistent decision.
|
|
50
|
-
**Fixed for one component** (gh#286); the general pattern is unswept.
|
|
31
|
+
1. **A browser-only API is called unconditionally → crash** (`attachInternals`, Observers, `adoptedStyleSheets`). **Fixed** (gh#285).
|
|
32
|
+
2. **`connectedCallback` destructively re-stamps existing DOM → silent content loss.** ORIGINAL diagnosis, narrowed to a static audit 2026-07-17 (gh#284) — doesn't currently expose against any shipped component.
|
|
33
|
+
2b. **Custom-element upgrade doesn't replay `attributeChangedCallback` for pre-existing attributes → reflected properties stuck at class default.** The REAL mechanism behind gh#284's symptom. **Fixed 2026-07-18** (PR #309).
|
|
34
|
+
3. **A connect-time layout measurement is treated as confirmed, not unknown.** **Fixed for one component** (gh#286); the general pattern is unswept.
|
|
35
|
+
4. **Property-only components can't seed initial state from SSR HTML** (gh#288) — a feature gap, **CLOSED 2026-07-18** (table-ui's `data="[…]"` attribute).
|
|
51
36
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
on shape 2/2b (table-ui/chart-ui/select-ui all use `static template = () => null`
|
|
55
|
-
too), and the real scope was narrower than filed: `select-ui` already declares
|
|
56
|
-
options via native `<option>` children, `chart-ui` already hydrates `.data` from a
|
|
57
|
-
JSON `data="[…]"` attribute — only `table-ui`'s `.data` had no declarative form,
|
|
58
|
-
now fixed the same way.
|
|
59
|
-
|
|
60
|
-
Full symptom → root-cause → status detail, cited to the actual shipped/open code:
|
|
61
|
-
[`references/failure-shapes.md`](references/failure-shapes.md).
|
|
37
|
+
Full symptom → root-cause → status detail, cited to the actual shipped/open
|
|
38
|
+
code: [failure-shapes.md](references/failure-shapes.md).
|
|
62
39
|
|
|
63
40
|
## Consult table
|
|
64
41
|
|
|
@@ -74,17 +51,12 @@ Full symptom → root-cause → status detail, cited to the actual shipped/open
|
|
|
74
51
|
|
|
75
52
|
## Deviation doctrine
|
|
76
53
|
|
|
77
|
-
Every fix pattern this pack cites ([
|
|
78
|
-
carries the reasoning for why it looks the way it does
|
|
79
|
-
shim exists because
|
|
80
|
-
it
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
unit suite is blind to that class by construction — deletion-based testing is the unit-level complement, not
|
|
84
|
-
the proof.
|
|
85
|
-
If a new case doesn't fit an existing pattern's reasoning, that's a signal to design a
|
|
86
|
-
new pattern. Route it through `primitive-authoring` — don't force-fit the nearest existing
|
|
87
|
-
shape.
|
|
54
|
+
Every fix pattern this pack cites ([guard-patterns.md](references/guard-patterns.md))
|
|
55
|
+
carries the reasoning for why it looks the way it does (e.g. the no-op
|
|
56
|
+
`ElementInternals` shim exists because `undefined` would relocate the crash,
|
|
57
|
+
not remove it). If a new case doesn't fit an existing pattern's reasoning,
|
|
58
|
+
that's a signal to design a new pattern — route through `primitive-authoring`,
|
|
59
|
+
don't force-fit the nearest existing shape.
|
|
88
60
|
|
|
89
61
|
## Boundaries
|
|
90
62
|
|
|
@@ -104,25 +76,11 @@ shape.
|
|
|
104
76
|
|
|
105
77
|
## Worked example — the answer contract
|
|
106
78
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
the CURRENT code before reusing the old answer, because this is exactly the case it
|
|
113
|
-
no longer covers. `text-ui`'s `static template` (`packages/web-components/components/text/text.class.js`)
|
|
114
|
-
is `() => null` — `connectedCallback`'s `if (result) stamp(result, this)` never
|
|
115
|
-
enters the branch, so `stamp()` never touches `<text-ui>`'s children at all. This
|
|
116
|
-
component isn't exposed to shape 2; something else is dropping the text — check
|
|
117
|
-
whether `text-ui` is even registered server-side (a different, structural gap: is
|
|
118
|
-
the tag defined before the SSR pass runs?), or whether another mutation (a parent
|
|
119
|
-
re-render, `innerHTML` elsewhere) is clearing it. **The general lesson, not just
|
|
120
|
-
this one component:** before answering "yes, known issue, shape 2" for ANY new
|
|
121
|
-
report, grep the component's own `static template` — if it's the literal
|
|
122
|
-
`() => null`, shape 2 cannot be the cause, no matter how closely the symptom
|
|
123
|
-
matches the old description. [`failure-shapes.md`](references/failure-shapes.md)
|
|
124
|
-
§2 has the full survey (150 components at the 2026-07 survey — the census has since grown) and cites exactly why every
|
|
125
|
-
current children-accepting component is unaffected.
|
|
79
|
+
A "content vanished" report can match shape 2's symptom while shape 2
|
|
80
|
+
itself no longer applies (it was narrowed 2026-07-17) — the general lesson
|
|
81
|
+
(grep the component's `static template` before answering "known issue,
|
|
82
|
+
shape 2") plus a full worked ask/answer are in
|
|
83
|
+
[failure-shapes.md](references/failure-shapes.md) §2's own Worked example.
|
|
126
84
|
|
|
127
85
|
## Corpus of record
|
|
128
86
|
|
|
@@ -131,6 +131,29 @@ the attribute-upgrade-replay gap was real-and-live (now fixed in #309). A
|
|
|
131
131
|
future "content vanished under SSR" report should check the attribute-replay
|
|
132
132
|
mechanism FIRST — it's the one that was actually firing.
|
|
133
133
|
|
|
134
|
+
### Worked example — answering a new "content vanished" report
|
|
135
|
+
|
|
136
|
+
**Ask:** "`<text-ui>Adia Admin</text-ui>` renders as an empty tag in our SSR
|
|
137
|
+
output — is this a known issue?"
|
|
138
|
+
|
|
139
|
+
**Answer:** It matches shape 2's SYMPTOM (`connectedCallback` destructively
|
|
140
|
+
re-stamping existing DOM), but shape 2 was narrowed on 2026-07-17 — verify
|
|
141
|
+
against the CURRENT code before reusing the old answer, because this is
|
|
142
|
+
exactly the case it no longer covers. `text-ui`'s `static template`
|
|
143
|
+
(`packages/web-components/components/text/text.class.js`) is `() => null` —
|
|
144
|
+
`connectedCallback`'s `if (result) stamp(result, this)` never enters the
|
|
145
|
+
branch, so `stamp()` never touches `<text-ui>`'s children at all. This
|
|
146
|
+
component isn't exposed to shape 2; something else is dropping the text —
|
|
147
|
+
check whether `text-ui` is even registered server-side (a different,
|
|
148
|
+
structural gap: is the tag defined before the SSR pass runs?), or whether
|
|
149
|
+
another mutation (a parent re-render, `innerHTML` elsewhere) is clearing
|
|
150
|
+
it. **The general lesson, not just this one component:** before answering
|
|
151
|
+
"yes, known issue, shape 2" for ANY new report, grep the component's own
|
|
152
|
+
`static template` — if it's the literal `() => null`, shape 2 cannot be the
|
|
153
|
+
cause, no matter how closely the symptom matches the old description. This
|
|
154
|
+
survey (150 components at the 2026-07 survey — the census has since grown)
|
|
155
|
+
cites exactly why every current children-accepting component is unaffected.
|
|
156
|
+
|
|
134
157
|
## 3 · A connect-time layout MEASUREMENT is meaningless before real layout exists
|
|
135
158
|
|
|
136
159
|
**Symptom:** a component makes a decision (a boolean state, a mode, a snapped value)
|