@adia-ai/adia-ui-forge 0.8.62 → 0.8.64
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/.codex-plugin/plugin.json +3 -3
- package/CHANGELOG.md +317 -275
- package/README.md +54 -18
- package/__init__.py +8 -8
- package/agents/a2ui-maintenance-agent.corpus.json +7 -7
- package/agents/a2ui-maintenance-agent.md +6 -6
- package/agents/a2ui-planner-agent.md +11 -11
- package/agents/demo-audit-agent.corpus.json +4 -4
- package/agents/demo-audit-agent.md +8 -9
- package/agents/framework-checker.md +10 -11
- package/agents/framework-planner-agent.md +9 -10
- package/agents/gen-ui-review-agent.corpus.json +4 -4
- package/agents/gen-ui-review-agent.md +8 -8
- package/agents/package-release-agent.corpus.json +4 -4
- package/agents/package-release-agent.md +9 -10
- package/agents/primitive-authoring-agent.corpus.json +1 -1
- package/agents/primitive-authoring-agent.md +5 -5
- package/agents/routing-corpus.json +46 -46
- package/codex.interface.json +1 -1
- package/commands/gen-ui-review.md +1 -1
- package/commands/package-release.md +2 -2
- package/package.json +1 -1
- package/plugin.yaml +2 -2
- package/prompts/gen-ui-review.md +1 -1
- package/prompts/package-release.md +2 -2
- package/references/contracts/a2ui-mcp-surface.md +7 -7
- package/references/contracts/migration-guide-format.md +6 -7
- package/references/shared/content-trust.md +3 -5
- package/skills/a2ui-maintenance/SKILL.md +13 -16
- package/skills/a2ui-maintenance/evals/evals.json +2 -2
- package/skills/a2ui-maintenance/evals/routing-corpus.json +1 -1
- package/skills/a2ui-maintenance/references/anti-patterns.md +10 -10
- package/skills/a2ui-maintenance/references/chunk-authoring.md +11 -12
- package/skills/a2ui-maintenance/references/corpus-discipline.md +13 -14
- package/skills/a2ui-maintenance/references/data-model-reactivity.md +22 -25
- package/skills/a2ui-maintenance/references/eval-diagnostics.md +21 -21
- package/skills/a2ui-maintenance/references/format-extension-decisions.md +12 -13
- package/skills/a2ui-maintenance/references/leverage-rules.md +6 -6
- package/skills/a2ui-maintenance/references/mcp-pipeline-ops.md +9 -10
- package/skills/a2ui-maintenance/references/mcp-tool-reference.md +11 -12
- package/skills/a2ui-maintenance/references/pipeline-overview.md +33 -38
- package/skills/a2ui-maintenance/references/semantic-fail-lifting.md +13 -14
- package/skills/a2ui-maintenance/references/strategy-engines.md +14 -16
- package/skills/a2ui-maintenance/references/surface-lifecycle.md +10 -11
- package/skills/a2ui-maintenance/references/zettel-calibration.md +14 -16
- package/skills/component-md-authoring/SKILL.md +22 -22
- package/skills/component-md-authoring/agents/openai.yaml +1 -1
- package/skills/cross-harness-authoring-standards/SKILL.md +19 -21
- package/skills/cross-harness-authoring-standards/agents/openai.yaml +1 -1
- package/skills/cross-harness-compatibility-standards/SKILL.md +35 -34
- package/skills/cross-harness-compatibility-standards/agents/openai.yaml +1 -1
- package/skills/demo-audit/SKILL.md +38 -38
- package/skills/demo-audit/evals/routing-corpus.json +1 -1
- package/skills/demo-audit/references/admin-shell-anatomy.md +21 -25
- package/skills/demo-audit/references/app-shell-pitfalls.md +11 -13
- package/skills/demo-audit/references/auto-fix-allowlist.md +3 -3
- package/skills/demo-audit/references/card-anatomy-sweep.md +10 -11
- package/skills/demo-audit/references/chat-shell-anatomy.md +25 -26
- package/skills/demo-audit/references/editor-shell-anatomy.md +25 -28
- package/skills/demo-audit/references/html-attr-sweep.md +12 -13
- package/skills/demo-audit/references/mode7-status-battery.md +4 -4
- package/skills/demo-audit/references/native-leak-annotations.md +8 -8
- package/skills/demo-audit/references/output-contract-worked-example.md +8 -8
- package/skills/demo-audit/references/probe-discipline-and-escalation.md +2 -2
- package/skills/demo-audit/references/visual-probe-triage.md +21 -23
- package/skills/gen-ui-review/SKILL.md +28 -28
- package/skills/gen-ui-review/agents/openai.yaml +1 -1
- package/skills/gen-ui-review/evals/evals.json +1 -1
- package/skills/gen-ui-review/evals/routing-corpus.json +1 -1
- package/skills/gen-ui-review/references/corpus-html-patterns.md +28 -29
- package/skills/gen-ui-review/references/exit-gate-mechanics.md +5 -5
- package/skills/gen-ui-review/references/lookup-maintenance.md +2 -2
- package/skills/gen-ui-review/references/loop-protocol.md +25 -25
- package/skills/gen-ui-review/references/rubric-cosmetic.md +10 -10
- package/skills/gen-ui-review/references/rubric-decompose.md +15 -16
- package/skills/gen-ui-review/references/rubric-score.md +23 -24
- package/skills/gen-ui-review/references/scorecard-worked-examples.md +12 -12
- package/skills/gen-ui-review/references/scores.schema.json +2 -2
- package/skills/llm-client-maintenance/SKILL.md +12 -12
- package/skills/llm-client-maintenance/evals/routing-corpus.json +1 -1
- package/skills/llm-client-maintenance/references/adapter-contract.md +27 -27
- package/skills/llm-client-maintenance/references/add-a-provider.md +25 -25
- package/skills/llm-client-maintenance/references/bridge-facade.md +31 -31
- package/skills/llm-client-maintenance/references/browser-proxy-boundary.md +17 -17
- package/skills/llm-client-maintenance/references/model-registry.md +14 -14
- package/skills/llm-client-maintenance/references/streaming-sse.md +21 -21
- package/skills/package-release/SKILL.md +21 -23
- package/skills/package-release/evals/evals.json +2 -2
- package/skills/package-release/evals/routing-corpus.json +2 -2
- package/skills/package-release/references/authorization-model.md +6 -8
- package/skills/package-release/references/changelog-discipline.md +25 -27
- package/skills/package-release/references/cut-procedure.md +129 -136
- package/skills/package-release/references/gates-catalog.md +42 -43
- package/skills/package-release/references/independent-package-release.md +14 -15
- package/skills/package-release/references/invariants-detail.md +9 -9
- package/skills/package-release/references/mechanization.md +11 -12
- package/skills/package-release/references/migration-guide-authoring.md +20 -20
- package/skills/package-release/references/notes-authoring.md +20 -20
- package/skills/package-release/references/recovery-paths.md +28 -28
- package/skills/primitive-authoring/SKILL.md +19 -19
- package/skills/primitive-authoring/agents/openai.yaml +1 -1
- package/skills/primitive-authoring/evals/evals.json +2 -2
- package/skills/primitive-authoring/evals/routing-corpus.json +2 -2
- package/skills/primitive-authoring/references/INDEX.md +22 -22
- package/skills/primitive-authoring/references/anti-patterns.md +76 -77
- package/skills/primitive-authoring/references/api-contract.md +63 -65
- package/skills/primitive-authoring/references/authoring-cycle.md +47 -50
- package/skills/primitive-authoring/references/canonical-pattern-index.md +118 -118
- package/skills/primitive-authoring/references/code-style.md +50 -52
- package/skills/primitive-authoring/references/common-gotchas.md +23 -23
- package/skills/primitive-authoring/references/composite-demo-protocol.md +42 -45
- package/skills/primitive-authoring/references/css-patterns.md +97 -97
- package/skills/primitive-authoring/references/for-attribute-event-contract.md +12 -12
- package/skills/primitive-authoring/references/form-control-sizing.md +16 -18
- package/skills/primitive-authoring/references/lifecycle-patterns.md +42 -42
- package/skills/primitive-authoring/references/llm-bridge.md +21 -21
- package/skills/primitive-authoring/references/module-promotion.md +51 -51
- package/skills/primitive-authoring/references/primitive-audit.md +16 -16
- package/skills/primitive-authoring/references/shell-patterns.md +81 -81
- package/skills/primitive-authoring/references/svg-authoring.md +62 -66
- package/skills/primitive-authoring/references/token-contract.md +45 -49
- package/skills/primitive-authoring/references/trait-pages.md +8 -8
- package/skills/primitive-authoring/references/worked-example.md +39 -39
- package/skills/primitive-authoring/references/yaml-contract.md +139 -143
- package/skills/site-deployment/SKILL.md +25 -26
- package/skills/site-deployment/agents/openai.yaml +1 -1
- package/skills/site-deployment/evals/routing-corpus.json +1 -1
- package/skills/site-deployment/references/deploy-playbooks.md +51 -53
- package/skills/site-docs-authoring/SKILL.md +14 -14
- package/skills/site-docs-authoring/agents/openai.yaml +1 -1
- package/skills/site-docs-authoring/evals/evals.json +1 -1
- package/skills/site-docs-authoring/evals/routing-corpus.json +1 -1
- package/skills/site-docs-authoring/intent.md +18 -18
- package/skills/ssr-compatibility/SKILL.md +24 -25
- package/skills/ssr-compatibility/agents/openai.yaml +1 -1
- package/skills/ssr-compatibility/evals/evals.json +1 -1
- package/skills/ssr-compatibility/evals/routing-corpus.json +1 -1
- package/skills/ssr-compatibility/references/consumer-workarounds.md +22 -23
- package/skills/ssr-compatibility/references/failure-shapes.md +84 -91
- package/skills/ssr-compatibility/references/guard-patterns.md +54 -58
- package/skills/ssr-compatibility/references/status-ledger.md +17 -17
- package/skills/ssr-compatibility/references/test-without-linkedom.md +26 -27
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
# Form-control sizing posture
|
|
1
|
+
# Form-control sizing posture, fill by default, `[inline]` hugs
|
|
2
2
|
|
|
3
3
|
Source of truth: [ADR-0077](../../../../../../docs/ops/adr/adr-0077-form-control-sizing-posture.md)
|
|
4
4
|
(ratified 2026-08-19; amends [ADR-0037](../../../../../../docs/ops/adr/adr-0037-inline-display-mode-convention.md)
|
|
5
|
-
partial
|
|
5
|
+
partial, `[inline]` semantics widen to own the sizing axis, the display-default
|
|
6
6
|
convention itself is unchanged). Companion SPEC:
|
|
7
7
|
`docs/ops/spec/spec-form-control-sizing-posture.md`. Read the ADR before
|
|
8
|
-
touching a form-control host's width/min-width rules
|
|
8
|
+
touching a form-control host's width/min-width rules: this file is the
|
|
9
9
|
routing pointer + the fix-now checklist, not a restatement of the ruling.
|
|
10
10
|
|
|
11
11
|
## The one-line rule
|
|
12
12
|
|
|
13
13
|
A form-control host, absent `[inline]`, is block-level (ADR-0037, unchanged)
|
|
14
|
-
**and fills its container's available inline space
|
|
14
|
+
**and fills its container's available inline space**, in block flow via
|
|
15
15
|
`width: auto` (already true), in flex/grid parents via `width: 100%` on the
|
|
16
16
|
host (the mechanism `field-ui`/`otp-input-ui` already shipped). `[inline]`
|
|
17
17
|
flips BOTH axes at once: inline-level display **and** shrink-wrap/hug sizing.
|
|
18
|
-
No separate `hug` attribute exists or should be minted
|
|
18
|
+
No separate `hug` attribute exists or should be minted, one token, one
|
|
19
19
|
posture flip.
|
|
20
20
|
|
|
21
21
|
## Where legibility floors live
|
|
@@ -23,7 +23,7 @@ posture flip.
|
|
|
23
23
|
**[retired 2026-08-28, ADR-0095]** The `--*-min-width: 20ch`-class floor
|
|
24
24
|
described below is no longer permitted anywhere, including on
|
|
25
25
|
`:scope[inline]`. A primitive never floors its own inline-size independent
|
|
26
|
-
of its own geometry
|
|
26
|
+
of its own geometry: the only permitted floor is
|
|
27
27
|
`min-inline-size: var(--<component>-height)` (a square minimum). ADR-0095
|
|
28
28
|
Decision Class 1 removes the 20ch-class field-width floor for
|
|
29
29
|
`input-ui`/`select-ui`/`combobox-ui` (plus `--input-label-min-width`,
|
|
@@ -32,21 +32,20 @@ floor, generalizing the `button-ui` keep-square pattern already named below
|
|
|
32
32
|
to the field-width components.
|
|
33
33
|
|
|
34
34
|
**[amended 2026-08-31, gh#2534, ADR-0095]** The square-minimum rule governs
|
|
35
|
-
a primitive's own OUTER inline-size floor against its own block-size only
|
|
36
|
-
it does not reach a component's INTERNAL grid-column sizing. A token like
|
|
35
|
+
a primitive's own OUTER inline-size floor against its own block-size only, it does not reach a component's INTERNAL grid-column sizing. A token like
|
|
37
36
|
`field-ui[inline]`'s `--field-label-inline-min: 10ch` floors the label
|
|
38
37
|
*column* inside the component's internal grid, to keep sibling rows
|
|
39
|
-
aligned
|
|
38
|
+
aligned, not to prevent the host itself from collapsing, so it is not
|
|
40
39
|
"a `--*-min-width: 20ch`-class floor" in ADR-0095's sense and is not
|
|
41
40
|
retired by the Decision above. Don't apply the square-minimum rule to an
|
|
42
41
|
internal grid-column token by analogy to the field-width class.
|
|
43
42
|
|
|
44
|
-
*(Historical, pre-ADR-0095 rule
|
|
43
|
+
*(Historical, pre-ADR-0095 rule, superseded by the retirement above, kept
|
|
45
44
|
only for context on what the old convention was.)* A
|
|
46
45
|
`--*-min-width: 20ch`-class floor (the gh#781/gh#1633 pattern on
|
|
47
46
|
`input-ui`/`select-ui`/`combobox-ui`) used to belong on `:scope[inline]`
|
|
48
47
|
only, never on the plain `:scope`. In the fill state the container owns the
|
|
49
|
-
width, all the way down
|
|
48
|
+
width, all the way down, no exception, no re-added floor. Two floor
|
|
50
49
|
classes are
|
|
51
50
|
explicitly NOT sizing-posture floors and stay in both states: inner
|
|
52
51
|
`min-width: 0` flex unclampers (the ellipsis-reset machinery) and
|
|
@@ -60,18 +59,17 @@ posture, and also stay in both states.
|
|
|
60
59
|
- Plain `:scope` (no `[inline]`): block-level, `width: auto` in block flow,
|
|
61
60
|
`width: 100%` in flex/grid parents. No `min-width` legibility floor here.
|
|
62
61
|
- `:scope[inline]`: inline-level display, shrink-wrap sizing. Per ADR-0095,
|
|
63
|
-
no `--*-min-width: 20ch`-class floor moves here any more
|
|
62
|
+
no `--*-min-width: 20ch`-class floor moves here any more, a floor-carrier
|
|
64
63
|
component uses the square minimum (`min-inline-size:
|
|
65
64
|
var(--<component>-height)`) instead, in both states.
|
|
66
65
|
- A component that wants to own its line but still hug has no attribute for
|
|
67
|
-
that state (ADR-0077 Decision item 6, accepted consequence)
|
|
66
|
+
that state (ADR-0077 Decision item 6, accepted consequence), tell the
|
|
68
67
|
consumer to use explicit sizing (`width: fit-content` or an explicit
|
|
69
68
|
width), don't invent a third posture token.
|
|
70
69
|
- `field-ui`/`fields-ui`'s `[inline]` carve-out (single-row layout meaning,
|
|
71
70
|
ADR-0037 §5) is retained and stays behaviorally convergent with this
|
|
72
|
-
decision
|
|
73
|
-
- The trip-wire gate is `scripts/release/check-display-default-convention.mjs`
|
|
74
|
-
— it fails a 20ch-class host floor on the plain `:scope` and fails a
|
|
71
|
+
decision, don't "fix" it to match the family's token meaning.
|
|
72
|
+
- The trip-wire gate is `scripts/release/check-display-default-convention.mjs`, it fails a 20ch-class host floor on the plain `:scope` and fails a
|
|
75
73
|
missing fill mechanism in the default state, for roster components. One
|
|
76
74
|
gate, no second check to satisfy.
|
|
77
75
|
|
|
@@ -79,6 +77,6 @@ posture, and also stay in both states.
|
|
|
79
77
|
|
|
80
78
|
The exact component roster and the breaking-wave/golden-regeneration rollout
|
|
81
79
|
(one coordinated wave, MIGRATION GUIDE entry same cycle, deliberately
|
|
82
|
-
regenerated `evals/visual/` goldens
|
|
80
|
+
regenerated `evals/visual/` goldens, never additive-first, never shimmed)
|
|
83
81
|
are the companion PLAN's job (`docs/ops/plan/plan-form-control-sizing.md`),
|
|
84
|
-
not this reference
|
|
82
|
+
not this reference, cite the ADR/PLAN rather than re-deriving scope here.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# Lifecycle Patterns
|
|
1
|
+
# Lifecycle Patterns, Listeners, Timers, Observers, Popovers
|
|
2
2
|
|
|
3
3
|
Deep dive on symmetric `connected()` / `disconnected()` in AdiaUI components. Read when adding any side effect (listener, timer, observer, popover) to a component, or when inheriting from `UIFormElement`.
|
|
4
4
|
|
|
5
5
|
## The symmetry rule
|
|
6
6
|
|
|
7
|
-
Every side effect set up in `connected()` has a matching teardown in `disconnected()`. The teardown is authored at the same time as the setup, not later
|
|
7
|
+
Every side effect set up in `connected()` has a matching teardown in `disconnected()`. The teardown is authored at the same time as the setup, not later, if you leave `disconnected()` for tomorrow, tomorrow won't happen.
|
|
8
8
|
|
|
9
9
|
```javascript
|
|
10
10
|
class UIWidget extends UIElement {
|
|
@@ -36,16 +36,16 @@ Four things happen in `disconnected()`:
|
|
|
36
36
|
3. [Not shown] Clear any timers.
|
|
37
37
|
4. [Not shown] Disconnect any observers.
|
|
38
38
|
|
|
39
|
-
## Stable handlers
|
|
39
|
+
## Stable handlers, `#field` arrows, never inline arrows
|
|
40
40
|
|
|
41
41
|
`removeEventListener(type, handler)` requires reference equality on `handler`. Inline arrows create a fresh function every time:
|
|
42
42
|
|
|
43
43
|
```javascript
|
|
44
|
-
// WRONG
|
|
44
|
+
// WRONG: the inline arrow is a new function every render
|
|
45
45
|
this.#buttonEl.addEventListener('click', (e) => this.doThing(e));
|
|
46
|
-
// Later: removeEventListener can't match
|
|
46
|
+
// Later: removeEventListener can't match, no reference equality.
|
|
47
47
|
|
|
48
|
-
// RIGHT
|
|
48
|
+
// RIGHT, stable reference via private field
|
|
49
49
|
#onButtonClick = (e) => this.doThing(e);
|
|
50
50
|
// connected:
|
|
51
51
|
this.#buttonEl.addEventListener('click', this.#onButtonClick);
|
|
@@ -57,10 +57,10 @@ The `#` prefix keeps the handler private to the class (can't be accidentally use
|
|
|
57
57
|
|
|
58
58
|
**Historical bugs this pattern prevents:**
|
|
59
59
|
|
|
60
|
-
- `cot.js:116
|
|
61
|
-
- Generic pattern across ~35 components in the audit cycle
|
|
60
|
+
- `cot.js:116`, summary click handler as inline arrow. Couldn't be removed; fixed by promoting to `#onSummaryClick`.
|
|
61
|
+
- Generic pattern across ~35 components in the audit cycle, all promoted to stable fields.
|
|
62
62
|
|
|
63
|
-
## Ephemeral DOM with inline arrows
|
|
63
|
+
## Ephemeral DOM with inline arrows, the tolerance rule
|
|
64
64
|
|
|
65
65
|
There's ONE exception: inline arrows on DOM elements that are guaranteed to be fully detached + GC'd before the next re-render.
|
|
66
66
|
|
|
@@ -78,12 +78,12 @@ There's ONE exception: inline arrows on DOM elements that are guaranteed to be f
|
|
|
78
78
|
}
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
**This is tolerated but not preferred.** If anything about the lifetime changes
|
|
81
|
+
**This is tolerated but not preferred.** If anything about the lifetime changes, if someone adds a `row.remove()` that leaves a cached reference, or converts to incremental DOM updates, the listeners stop getting GC'd and become leaks.
|
|
82
82
|
|
|
83
83
|
The safer pattern is event delegation on the stable parent:
|
|
84
84
|
|
|
85
85
|
```javascript
|
|
86
|
-
// Preferred
|
|
86
|
+
// Preferred, one listener on the parent, reads data-attributes
|
|
87
87
|
connected() {
|
|
88
88
|
this.#container.addEventListener('click', this.#onContainerClick);
|
|
89
89
|
}
|
|
@@ -102,7 +102,7 @@ disconnected() {
|
|
|
102
102
|
|
|
103
103
|
One listener, always removable, survives DOM churn.
|
|
104
104
|
|
|
105
|
-
## Timers
|
|
105
|
+
## Timers, `setInterval`, `setTimeout`
|
|
106
106
|
|
|
107
107
|
Store the handle, clear it in `disconnected()`:
|
|
108
108
|
|
|
@@ -128,7 +128,7 @@ disconnected() {
|
|
|
128
128
|
|
|
129
129
|
The null-guard is defensive; `clearInterval(null)` is actually a no-op but the guard makes the "was it created?" intent explicit.
|
|
130
130
|
|
|
131
|
-
## Observers
|
|
131
|
+
## Observers, ResizeObserver, MutationObserver, IntersectionObserver
|
|
132
132
|
|
|
133
133
|
Every observer has `.disconnect()`:
|
|
134
134
|
|
|
@@ -146,28 +146,28 @@ disconnected() {
|
|
|
146
146
|
}
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
**Historical bug:** `chart.js` had TWO `disconnected()` methods
|
|
149
|
+
**Historical bug:** `chart.js` had TWO `disconnected()` methods, the second one silently overrode the first. The first had the `ResizeObserver.disconnect()` call; the second didn't. Chart listeners leaked until a production repro surfaced it.
|
|
150
150
|
|
|
151
|
-
**Lesson:** a class must declare `disconnected()` exactly once. If you need to add teardown, edit the existing method. Duplicate method names silently shadow
|
|
151
|
+
**Lesson:** a class must declare `disconnected()` exactly once. If you need to add teardown, edit the existing method. Duplicate method names silently shadow, no error, no warning.
|
|
152
152
|
|
|
153
|
-
## Reflection
|
|
153
|
+
## Reflection, non-finite Number-typed props don't stringify
|
|
154
154
|
|
|
155
|
-
`reflect()` in `core/element.js` writes a `Number`-typed reflected property's value back onto the DOM attribute by stringifying it. That's correct for finite numbers, but a non-numeric sentinel assigned to a `Number`-typed reflected prop (e.g. `range-total="?"
|
|
155
|
+
`reflect()` in `core/element.js` writes a `Number`-typed reflected property's value back onto the DOM attribute by stringifying it. That's correct for finite numbers, but a non-numeric sentinel assigned to a `Number`-typed reflected prop (e.g. `range-total="?"`, table-ui's open/unproven-total marker for cursor/hasMore server paging) used to get silently coerced and re-stringified as the literal attribute `range-total="NaN"`, before any render ran (gh#1895, PR #1907, ADR-0082 amendment).
|
|
156
156
|
|
|
157
|
-
**Fix, framework-wide:** a non-finite `Number`-typed value is left unreflected instead of stringified
|
|
157
|
+
**Fix, framework-wide:** a non-finite `Number`-typed value is left unreflected instead of stringified, gated narrowly on `Number` type + `Number.isFinite()` failing, so no other reflected prop's behavior changes.
|
|
158
158
|
|
|
159
159
|
```javascript
|
|
160
|
-
// core/element.js reflect()
|
|
160
|
+
// core/element.js reflect(), the narrow gate
|
|
161
161
|
if (type === Number && !Number.isFinite(value)) {
|
|
162
|
-
// leave unreflected
|
|
162
|
+
// leave unreflected, do NOT write "NaN" to the attribute
|
|
163
163
|
} else {
|
|
164
164
|
this.setAttribute(attr, String(value));
|
|
165
165
|
}
|
|
166
166
|
```
|
|
167
167
|
|
|
168
|
-
**Recognition:** any future non-numeric sentinel authored onto a `Number`-typed reflected prop (a `'?'`, an `'auto'`, an open-ended marker) hits the same wart unless it too fails `Number.isFinite()` before the stringify step. If a prop's contract legitimately mixes numbers with a sentinel string, declare it non-reflecting or widen its declared type
|
|
168
|
+
**Recognition:** any future non-numeric sentinel authored onto a `Number`-typed reflected prop (a `'?'`, an `'auto'`, an open-ended marker) hits the same wart unless it too fails `Number.isFinite()` before the stringify step. If a prop's contract legitimately mixes numbers with a sentinel string, declare it non-reflecting or widen its declared type, don't rely on the reflect() gate alone as documentation of the contract.
|
|
169
169
|
|
|
170
|
-
## `UIFormElement
|
|
170
|
+
## `UIFormElement`, `super` discipline
|
|
171
171
|
|
|
172
172
|
Form-participating components inherit `ElementInternals` wiring from `UIFormElement`. That wiring only works if `super.connected()` and `super.disconnected()` are called:
|
|
173
173
|
|
|
@@ -185,20 +185,20 @@ class UIInput extends UIFormElement {
|
|
|
185
185
|
#onInput = () => this.syncValue(this.#inputEl?.value ?? '');
|
|
186
186
|
|
|
187
187
|
connected() {
|
|
188
|
-
super.connected(); // MUST
|
|
188
|
+
super.connected(); // MUST, registers ElementInternals, form association
|
|
189
189
|
this.#inputEl = this.querySelector('input');
|
|
190
190
|
this.#inputEl?.addEventListener('input', this.#onInput);
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
disconnected() {
|
|
194
|
-
super.disconnected(); // MUST
|
|
194
|
+
super.disconnected(); // MUST, unregisters form participation
|
|
195
195
|
this.#inputEl?.removeEventListener('input', this.#onInput);
|
|
196
196
|
this.#inputEl = null;
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
-
Omit `super.connected()` and the component won't participate in form submission
|
|
201
|
+
Omit `super.connected()` and the component won't participate in form submission: the field value is never collected, no validation fires, no form-reset handler runs. It's a silent failure; you only find out when form submission misses the field.
|
|
202
202
|
|
|
203
203
|
## Popovers, tooltips, document-level overlays
|
|
204
204
|
|
|
@@ -290,7 +290,7 @@ disconnected() {
|
|
|
290
290
|
}
|
|
291
291
|
```
|
|
292
292
|
|
|
293
|
-
## DOM-reference nulling
|
|
293
|
+
## DOM-reference nulling, why it matters
|
|
294
294
|
|
|
295
295
|
A cached DOM reference (`this.#fooEl`) held by the component class keeps the referenced element alive for GC purposes, along with its own subtree. When the component is removed from the DOM, the old tree hangs around in memory until the component itself is garbage-collected.
|
|
296
296
|
|
|
@@ -306,36 +306,36 @@ disconnected() {
|
|
|
306
306
|
}
|
|
307
307
|
```
|
|
308
308
|
|
|
309
|
-
Especially important for components that are attached/detached frequently (modals, toasts, drawers)
|
|
309
|
+
Especially important for components that are attached/detached frequently (modals, toasts, drawers), each incarnation leaks its old subtree into memory if refs aren't released.
|
|
310
310
|
|
|
311
|
-
## Adopt-or-stamp
|
|
311
|
+
## Adopt-or-stamp, slot content is the source of truth
|
|
312
312
|
|
|
313
313
|
Every slot-bearing primitive that exposes both an attribute and a slot for the same logical content (`text=` + `<span slot="text">`) follows the `#stampMark` / `#wasStamped` policy (working reference: `list-item-ui`, `packages/web-components/components/list/list.js`):
|
|
314
314
|
|
|
315
315
|
1. `connected()` either **adopts** the consumer-authored slotted child or **stamps** a new one, tagging it `data-{component}-stamped`.
|
|
316
|
-
2. `render()` may only mutate or remove elements carrying the stamp. An unstamped slotted child is consumer-owned
|
|
316
|
+
2. `render()` may only mutate or remove elements carrying the stamp. An unstamped slotted child is consumer-owned, leave it alone, even when the matching attribute is empty.
|
|
317
317
|
3. The attribute stays a convenience shorthand that produces a stamped element; both forms render identically and are interchangeable.
|
|
318
318
|
|
|
319
|
-
The failure this prevents: a `render()` that overwrites `textContent` from the attribute, or hides/removes the slot element when the attribute is empty, silently destroys consumer-authored rich content (links, `<strong>`, inline icons). A 13-page audit found 73 slot uses where the slotted form is the only way to author the intended content (the destructive render() confirmed in 3 primitives) before the policy landed
|
|
319
|
+
The failure this prevents: a `render()` that overwrites `textContent` from the attribute, or hides/removes the slot element when the attribute is empty, silently destroys consumer-authored rich content (links, `<strong>`, inline icons). A 13-page audit found 73 slot uses where the slotted form is the only way to author the intended content (the destructive render() confirmed in 3 primitives) before the policy landed, from the page author's seat it looks like "the docs are broken", from the component author's seat like "the consumer didn't pass the attribute"; both are wrong-blame. Source: ADR-0010.
|
|
320
320
|
|
|
321
|
-
### `slot="caret"
|
|
321
|
+
### `slot="caret"`, the one disclosure-indicator slot
|
|
322
322
|
|
|
323
|
-
The expand/collapse (and dropdown) directional-indicator slot is named `caret` on every primitive that exposes composition slots (`tree-item-ui`, `pane-ui`, `accordion-item-ui`, `nav-group-ui`, `select-ui`, `drilldown-ui`). `chevron` is retired
|
|
323
|
+
The expand/collapse (and dropdown) directional-indicator slot is named `caret` on every primitive that exposes composition slots (`tree-item-ui`, `pane-ui`, `accordion-item-ui`, `nav-group-ui`, `select-ui`, `drilldown-ui`). `chevron` is retired, a hard rename with no compat alias, and `--*-chevron-*` tokens became `--*-caret-*` (Phosphor names the glyphs `caret-right` / `caret-down`; there is no `chevron-*` icon).
|
|
324
324
|
|
|
325
325
|
- The mechanism is adopt-or-stamp (above): honor a declarative `<… slot="caret">` child if present, else stamp `<icon-ui slot="caret" name="caret-right">`. The same adopt-or-stamp rule applies to **every** composition slot the stamp builds (e.g. `slot="actions"`).
|
|
326
|
-
- Rotation is driven by host open-state CSS (`:scope[open]` / `[collapsed]` / `[expanded]`)
|
|
327
|
-
- Internal-render components with no composition slots (`agent-trace`, `agent-reasoning`) keep `[data-*-caret]` styling hooks
|
|
326
|
+
- Rotation is driven by host open-state CSS (`:scope[open]` / `[collapsed]` / `[expanded]`), never by swapping the icon name.
|
|
327
|
+
- Internal-render components with no composition slots (`agent-trace`, `agent-reasoning`) keep `[data-*-caret]` styling hooks, the convention governs slots, not internal hooks.
|
|
328
328
|
|
|
329
329
|
Source: ADR-0036, ADR-0092 (`drilldown-ui`'s row-level caret migrated from `slot="chevron"`,
|
|
330
330
|
retained under ADR-0085, to `slot="caret"`, reversing that exemption).
|
|
331
331
|
|
|
332
332
|
## Anti-patterns to watch for
|
|
333
333
|
|
|
334
|
-
- **Inline arrow + `removeEventListener
|
|
335
|
-
- **Forgot to create `disconnected()
|
|
336
|
-
- **Two `disconnected()` methods in the same class
|
|
337
|
-
- **`super.connected()` missing in `UIFormElement` subclass
|
|
338
|
-
- **Popover / tooltip never cleaned up
|
|
339
|
-
- **Observer recreated on every render
|
|
340
|
-
- **Timer fired after component removed
|
|
341
|
-
- **Multi-`await` method that can be re-invoked before the previous call finishes** (a route change, search-as-you-type, tab switch)
|
|
334
|
+
- **Inline arrow + `removeEventListener`**, the listener isn't really being removed. Silent leak.
|
|
335
|
+
- **Forgot to create `disconnected()`**, if `connected()` adds side effects, `disconnected()` must exist. No exceptions.
|
|
336
|
+
- **Two `disconnected()` methods in the same class**, the second silently wins; the first's cleanup is lost.
|
|
337
|
+
- **`super.connected()` missing in `UIFormElement` subclass**, form participation silently fails.
|
|
338
|
+
- **Popover / tooltip never cleaned up**, stays in `document.body` forever.
|
|
339
|
+
- **Observer recreated on every render**, re-observing without disconnecting first leaks observer refs.
|
|
340
|
+
- **Timer fired after component removed**, not cleared in `disconnected()`, fires on a dead component. Can cause null-deref crashes or phantom re-renders.
|
|
341
|
+
- **Multi-`await` method that can be re-invoked before the previous call finishes** (a route change, search-as-you-type, tab switch), a guard checked only at ONE checkpoint doesn't protect the awaits after it; a slow-but-eventually-resuming call can pass every checkpoint it reaches and clobber a newer call's result. See [common-gotchas.md §6](common-gotchas.md#6-async-loadrender-function-completing-out-of-order--a-guard-at-the-checkpoint-isnt-enough) (the sequence-token pattern: claim before the first `await`, re-check after every subsequent one).
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# LLM Bridge extension
|
|
1
|
+
# LLM Bridge extension, @adia-ai/llm
|
|
2
2
|
|
|
3
|
-
**Superseded 2026-07-18
|
|
3
|
+
**Superseded 2026-07-18, `@adia-ai/llm` internals are `llm-client-maintenance`'s charter, not
|
|
4
4
|
`primitive-authoring`'s.** That skill's references (`adapter-contract.md`, `streaming-sse.md`,
|
|
5
5
|
`model-registry.md`, `bridge-facade.md`, `browser-proxy-boundary.md`, `add-a-provider.md`)
|
|
6
|
-
are the current, maintained source
|
|
6
|
+
are the current, maintained source: this file is kept only as a historical pointer and is
|
|
7
7
|
no longer linked from `primitive-authoring`'s task-shape table or `code-style.md`. Route provider/
|
|
8
8
|
streaming/bridge work to `llm-client-maintenance`.
|
|
9
9
|
|
|
@@ -47,13 +47,13 @@ This reference is about **modifying** the package. For day-to-day consumption (i
|
|
|
47
47
|
└── server.js ← Local proxy + static server (NOT shipped to npm)
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
## Hard rules
|
|
50
|
+
## Hard rules, defaults that bit us
|
|
51
51
|
|
|
52
52
|
### Rule 1: `maxTokens: 32768` is intentional
|
|
53
53
|
|
|
54
54
|
The bridge hardcodes `maxTokens: 32768` in `complete()` and `stream()`. **Don't lower this.**
|
|
55
55
|
|
|
56
|
-
A2UI JSON for moderately complex UIs (kanban, dashboard, pricing tier table) routinely exceeds 8k. An 8k truncation was discovered producing silent fallbacks the validator rubber-stamped at ~89/100
|
|
56
|
+
A2UI JSON for moderately complex UIs (kanban, dashboard, pricing tier table) routinely exceeds 8k. An 8k truncation was discovered producing silent fallbacks the validator rubber-stamped at ~89/100: the generator looked healthy but was emitting incomplete component trees.
|
|
57
57
|
|
|
58
58
|
If you're adding a new provider that has a lower maxTokens ceiling (e.g., a model with 4k output limit), surface it as a model-level constraint in `MODELS`, **don't bypass the 32k bridge default**.
|
|
59
59
|
|
|
@@ -66,18 +66,18 @@ The bridge passes `cache: this.#provider === 'anthropic'`. This marks the ~23KB
|
|
|
66
66
|
- Below provider's minimum cacheable size (1024 tok Sonnet/Opus, 2048 Haiku): no-op
|
|
67
67
|
- Other providers silently ignore the unknown opt
|
|
68
68
|
|
|
69
|
-
When adding a new provider, leave `cache` falsy unless that provider has its own caching mechanism
|
|
69
|
+
When adding a new provider, leave `cache` falsy unless that provider has its own caching mechanism, then add a parallel `case` in the adapter's `buildRequest`.
|
|
70
70
|
|
|
71
71
|
### Rule 3: stopReason must propagate
|
|
72
72
|
|
|
73
73
|
The bridge surfaces `stopReason` from the upstream response in both `complete()` and `stream()`'s terminal `done` chunk. Values:
|
|
74
74
|
|
|
75
|
-
- `'end'` / `'stop'` / `'STOP'
|
|
76
|
-
- `'max_tokens'` / `'length'` / `'MAX_TOKENS'` (Gemini)
|
|
77
|
-
- `'tool_use'` / `'tool_calls'
|
|
78
|
-
- `'error'
|
|
75
|
+
- `'end'` / `'stop'` / `'STOP'`, clean completion
|
|
76
|
+
- `'max_tokens'` / `'length'` / `'MAX_TOKENS'` (Gemini), **truncation**
|
|
77
|
+
- `'tool_use'` / `'tool_calls'`, tool-call paused output
|
|
78
|
+
- `'error'`, provider-side error
|
|
79
79
|
|
|
80
|
-
**Downstream parser refuses silent fallback rendering when stopReason indicates truncation.** Don't normalize all values to `'end'
|
|
80
|
+
**Downstream parser refuses silent fallback rendering when stopReason indicates truncation.** Don't normalize all values to `'end'`, the generator's truncation detector reads the raw value.
|
|
81
81
|
|
|
82
82
|
### Rule 4: lazy-load adapters in browser-facing modules
|
|
83
83
|
|
|
@@ -164,7 +164,7 @@ Edit `packages/llm/core/package.json`:
|
|
|
164
164
|
}
|
|
165
165
|
```
|
|
166
166
|
|
|
167
|
-
The `./adapters/*` glob already covers new files
|
|
167
|
+
The `./adapters/*` glob already covers new files, no edit needed unless you want a named subpath.
|
|
168
168
|
|
|
169
169
|
### Step 4: Update `models.js` catalog
|
|
170
170
|
|
|
@@ -227,16 +227,16 @@ LLM_PROVIDER=myprovider node packages/gen-ui/mcp/gen-ui/scripts/eval-diff.mjs --
|
|
|
227
227
|
|
|
228
228
|
## Pitfalls
|
|
229
229
|
|
|
230
|
-
- **Two proxy shapes
|
|
230
|
+
- **Two proxy shapes, `proxyUrl` is overloaded.** The bridge supports two architecturally different proxies and the dispatcher in `packages/llm/core/adapters/index.js` chooses between them by URL shape. Get this wrong and you get silent 401s in the browser even though `node packages/llm/core/server.js` works fine.
|
|
231
231
|
- **Smart proxy** (`packages/llm/core/server.js`, route `POST /api/chat`): accepts a **provider-neutral body** `{ provider, model, messages, ... }`, holds the API key server-side, and dispatches internally to the right adapter. Headers are just `content-type: application/json`.
|
|
232
|
-
- **Passthrough proxy** (Vite dev `/api/llm/<provider>/<rest>` → real upstream URL `https://api.<provider>.com/<rest>`): expects the **real upstream body shape** (Anthropic-shaped for `/anthropic/`, OpenAI-shaped for `/openai/`, etc.) plus the **adapter's own auth headers** (`x-api-key` + `anthropic-version`, or `Authorization: Bearer …`). The proxy is dumb
|
|
232
|
+
- **Passthrough proxy** (Vite dev `/api/llm/<provider>/<rest>` → real upstream URL `https://api.<provider>.com/<rest>`): expects the **real upstream body shape** (Anthropic-shaped for `/anthropic/`, OpenAI-shaped for `/openai/`, etc.) plus the **adapter's own auth headers** (`x-api-key` + `anthropic-version`, or `Authorization: Bearer …`). The proxy is dumb, it just rewrites the URL and forwards bytes.
|
|
233
233
|
- Dispatcher: `chat()` and `streamChat()` in `adapters/index.js` call `isPassthroughProxy(proxyUrl)` (regex `/\/api\/llm\/[a-z]+(\/|$)/`). If true, they call `passthroughRequest()` which builds the real upstream body via `adapter.buildRequest()` then swaps in `proxyUrl`. If false, they call `proxyRequest()` with the provider-neutral body.
|
|
234
|
-
- **When adding a new provider**, make sure: (1) `buildRequest()` omits the API key header when `proxyUrl` is the smart proxy (already handled), (2) `buildRequest()` _includes_ the API key header when called from `passthroughRequest()` (also already handled
|
|
234
|
+
- **When adding a new provider**, make sure: (1) `buildRequest()` omits the API key header when `proxyUrl` is the smart proxy (already handled), (2) `buildRequest()` _includes_ the API key header when called from `passthroughRequest()` (also already handled, passthroughRequest forwards adapter headers). The adapter's `buildRequest()` is the single source of truth for upstream-shape body + headers. Don't fork it for proxy mode.
|
|
235
235
|
- **Don't add `apiKey` to the proxy body**. The proxy holds the key server-side. Adapter's `buildRequest` should set the API-key header only when `proxyUrl` is unset (i.e., direct mode).
|
|
236
236
|
- **Don't normalize `stopReason`**. Each provider uses different terminal values (`max_tokens` vs `length` vs `MAX_TOKENS`); the truncation detector reads them raw.
|
|
237
237
|
- **Don't import the package from itself.** `index.js` re-exports from `./adapters/index.js`; consumers inside the package reference relative paths to dodge the package-name resolution loop.
|
|
238
|
-
- **`MODELS` is consumed by `<chat-input-ui
|
|
239
|
-
- **`DEFAULT_MODEL` is the cheapest option
|
|
238
|
+
- **`MODELS` is consumed by `<chat-input-ui>`**, its shape is `[{ label, options: [{ value, label }] }]` matching `<select-ui>` with `<optgroup>`s. Don't deviate from this shape or 3 apps break.
|
|
239
|
+
- **`DEFAULT_MODEL` is the cheapest option**, resist the urge to default to a flagship model, most consumers want fast/cheap by default.
|
|
240
240
|
|
|
241
241
|
## Verification gates
|
|
242
242
|
|
|
@@ -253,7 +253,7 @@ If you added a new SSE chunk type to the streaming protocol, verify all 7 consum
|
|
|
253
253
|
|
|
254
254
|
## Cross-references
|
|
255
255
|
|
|
256
|
-
- [code-style.md](code-style.md)
|
|
257
|
-
- **a2ui-maintenance** (sibling skill)
|
|
258
|
-
- Spec: `.claude/docs/specs/package-architecture.md` § 11 (Phase 5
|
|
259
|
-
- README: `packages/llm/core/README.md
|
|
256
|
+
- [code-style.md](code-style.md), general AdiaUI conventions (this is a specialty path; start there if you need broader context)
|
|
257
|
+
- **a2ui-maintenance** (sibling skill), generator / MCP / zettel synthesis (consumes the bridge)
|
|
258
|
+
- Spec: `.claude/docs/specs/package-architecture.md` § 11 (Phase 5, engine registry)
|
|
259
|
+
- README: `packages/llm/core/README.md`, consumer-facing docs
|