@adia-ai/adia-ui-forge 0.8.58 → 0.8.60
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 +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +61 -0
- package/__init__.py +2 -2
- package/package.json +1 -1
- package/plugin.yaml +1 -1
- package/references/contracts/a2ui-mcp-surface.md +1 -1
- package/scripts/lint-rules.generated.mjs +11554 -1120
- package/skills/a2ui-maintenance/references/pipeline-overview.md +31 -1
- package/skills/component-md-authoring/SKILL.md +18 -14
- package/skills/demo-audit/SKILL.md +3 -3
- package/skills/demo-audit/agents/openai.yaml +1 -1
- package/skills/demo-audit/references/app-shell-pitfalls.md +6 -1
- package/skills/demo-audit/references/chat-shell-anatomy.md +98 -0
- package/skills/demo-audit/references/editor-shell-anatomy.md +109 -0
- package/skills/gen-ui-review/scripts/gen-review-decompose.mjs +27 -34
- package/skills/gen-ui-review/scripts/overflow-detect.generated.mjs +101 -0
- package/skills/package-release/references/cut-procedure.md +90 -5
- package/skills/package-release/references/recovery-paths.md +20 -0
- package/skills/package-release/scripts/bump.mjs +77 -8
- package/skills/package-release/scripts/gate-roster.mjs +27 -0
- package/skills/package-release/scripts/release-pack.mjs +481 -45
- package/skills/primitive-authoring/references/yaml-contract.md +106 -9
|
@@ -154,7 +154,13 @@ for any constant or decision lives in git and PR descriptions
|
|
|
154
154
|
7. **Registry ↔ catalog parity.** A component in the runtime registry but
|
|
155
155
|
missing from catalog schemas silently drops from generated compositions —
|
|
156
156
|
`npm run check:registry-catalog-coherence` guards it; run it after catalog
|
|
157
|
-
changes.
|
|
157
|
+
changes. `packages/gen-ui/a2ui/registry.js` is Class R (ADR-0069,
|
|
158
|
+
gh#3055): never hand-edit it — a new component enters through its yaml
|
|
159
|
+
`component:`/`tag:` fields, an alias or native-element mapping through
|
|
160
|
+
`packages/gen-ui/a2ui/registry.exceptions.json`, then
|
|
161
|
+
`node scripts/build/a2ui-registry.mjs` (derived-resync regenerates it on
|
|
162
|
+
main; `check:a2ui-registry` is the advisory freshness gate,
|
|
163
|
+
`check:a2ui-registry:validate` the blocking validity gate).
|
|
158
164
|
8. **Multi-turn emits A2UI `updateComponents` messages, not new compositions.**
|
|
159
165
|
The chunk-refiner mutates the binding plan via four ops (`rebindSlot` /
|
|
160
166
|
`appendToSlot` / `removeFromSlot` / `replacePage`); state chains via
|
|
@@ -187,6 +193,30 @@ for any constant or decision lives in git and PR descriptions
|
|
|
187
193
|
SoT); ADR-0097 rules a fourth, `traits`, but that part is decided-not-
|
|
188
194
|
yet-shipped (gh#2513) — see `primitive-authoring/references/
|
|
189
195
|
yaml-contract.md` §Synthesized universal props for the full contract.
|
|
196
|
+
14. **Provider "extended thinking" is a strategy-level opt-in, not a
|
|
197
|
+
global default** (gh#3516, LLD-0033). PR #3511 (gh#3477) made
|
|
198
|
+
`{ thinking, thinkingBudget }` reachable through
|
|
199
|
+
`AdiaUILLMBridge.complete()`/`stream()`, default off; two call sites
|
|
200
|
+
opt in for real, each behind its own fixed-constant budget rather than
|
|
201
|
+
a caller-threaded option: `generate-thinking.js`'s own generate call
|
|
202
|
+
(`THINKING_BUDGET_MONOLITHIC_THINKING`) and `free-form-composer/
|
|
203
|
+
index.js`'s ingredient-picker call, both its primary pick and its own
|
|
204
|
+
paraphrase-retry (`THINKING_BUDGET_FREE_FORM`), both starting at the
|
|
205
|
+
bridge's own `DEFAULT_THINKING_BUDGET` (10000). `auto` inherits
|
|
206
|
+
through the free-form picker call the moment it escalates that far -
|
|
207
|
+
`monolithic-thinking` itself is never reachable via `auto`'s own
|
|
208
|
+
escalation ladder. Every other LLM call site (`generate-pro.js`'s four
|
|
209
|
+
branches, zettel's locator/modifier/synthesizer, the shared
|
|
210
|
+
`validate-and-repair.js` repair loop) stays thinking-off deliberately,
|
|
211
|
+
a narrow first pass pending real eval numbers - do not assume a new
|
|
212
|
+
engine inherits thinking by proximity to one that has it.
|
|
213
|
+
`StubLLMAdapter` accepts and records `thinking`/`thinkingBudget` on
|
|
214
|
+
its own `calls` log for strategy-level test assertions.
|
|
215
|
+
`eval-diff.mjs` gained `--mode` (`instant`/`pro`/`thinking`), scoped
|
|
216
|
+
to `--engine mcp` only, so `--engine mcp --mode thinking` exercises
|
|
217
|
+
the opted-in path independently of the router's own default. See
|
|
218
|
+
`docs/ops/lld/lld-0033-strategy-thinking-opt-in.md` for the full
|
|
219
|
+
decision record and the conductor's D1-D5 rulings.
|
|
190
220
|
|
|
191
221
|
## Test + run commands (all verified in root package.json)
|
|
192
222
|
|
|
@@ -6,7 +6,7 @@ description: >-
|
|
|
6
6
|
component's states, composed children, aria behavior, or error/empty/
|
|
7
7
|
loading handling changes and it already has (or should grow) a
|
|
8
8
|
`component.md`, or when asked to "add component.md for X" / "write the
|
|
9
|
-
screen-reader spec for X" / "why
|
|
9
|
+
screen-reader spec for X" / "why is check:component-md-fresh warning". NOT
|
|
10
10
|
the yaml prop/slot/event/token contract itself (primitive-authoring owns
|
|
11
11
|
that — this skill only owns the two authored yaml fields,
|
|
12
12
|
`screenReader`/`behavioral`, plus the optional `intent` field); NOT gen-ui
|
|
@@ -39,14 +39,16 @@ regenerated from them. This is deliberate, not incidental:
|
|
|
39
39
|
gaps — screen-reader and behavioral judgment — get the SAME treatment:
|
|
40
40
|
authored once, in yaml, transcluded everywhere else (component.md today;
|
|
41
41
|
gen-ui corpus derivation once a2ui-maintenance wires it in).
|
|
42
|
-
-
|
|
43
|
-
authored content lives in a yaml
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
- **`component.md` is Class R, derived on main, not authored in the PR
|
|
43
|
+
(gh#3172, ADR-0069).** Because the authored content lives in a yaml
|
|
44
|
+
field, `component.md` is 100% mechanically regenerable — a PR commits
|
|
45
|
+
only the `screenReader`/`behavioral` yaml edit; `check:component-md-fresh`
|
|
46
|
+
runs advisory-only inside `check:pr-ready` (WARN, never fails the run)
|
|
47
|
+
and the `push: main` `derived-resync` job regenerates `component.md`
|
|
48
|
+
itself once the PR merges. A hand-edit directly in `component.md` will
|
|
49
|
+
still be silently clobbered by the next `npm run docs:component-md` or
|
|
50
|
+
by `derived-resync` on main — that's the guard rail, not a bug, even
|
|
51
|
+
though nothing blocks the PR on it.
|
|
50
52
|
|
|
51
53
|
## Authoring a component's two sections
|
|
52
54
|
|
|
@@ -79,7 +81,7 @@ regenerated from them. This is deliberate, not incidental:
|
|
|
79
81
|
```bash
|
|
80
82
|
node scripts/build/components.mjs --validate # schema-valid yaml
|
|
81
83
|
npm run docs:component-md # regenerate component.md
|
|
82
|
-
npm run check:component-md-fresh #
|
|
84
|
+
npm run check:component-md-fresh # advisory gate (check:pr-ready); derived-resync owns main
|
|
83
85
|
```
|
|
84
86
|
|
|
85
87
|
6. If this is the component's FIRST component.md (yaml previously had
|
|
@@ -110,7 +112,9 @@ defeats the point of a rollout plan).
|
|
|
110
112
|
- `scripts/schemas/component.yaml.schema.json` — `intent`/`screenReader`/
|
|
111
113
|
`behavioral` field definitions (all optional; a component with a `.yaml`
|
|
112
114
|
but neither authored field simply has no `component.md` yet).
|
|
113
|
-
- `scripts/verify/check-component-md-fresh.mjs` — the
|
|
114
|
-
byte-freshness (component.md matches a fresh render)
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
- `scripts/verify/check-component-md-fresh.mjs` — the freshness gate:
|
|
116
|
+
byte-freshness (component.md matches a fresh render). Advisory-only in
|
|
117
|
+
`check:pr-ready` (gh#3172, ADR-0069) — a PR commits the yaml edit alone
|
|
118
|
+
and `derived-resync` regenerates `component.md` on `push: main`; the
|
|
119
|
+
same-PR coverage check this gate used to run was removed outright
|
|
120
|
+
(LLD-0020 §1c), not demoted.
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
name: demo-audit
|
|
3
3
|
description: >-
|
|
4
4
|
Seven-mode QA sweep of the monorepo's demo/app surfaces: visual probe,
|
|
5
|
-
app-shell QA, attr-quote typos, native-primitive leak, admin
|
|
6
|
-
composition, card anatomy, plus an aggregated token/contrast/lifecycle
|
|
5
|
+
app-shell QA, attr-quote typos, native-primitive leak, shell (admin/chat/
|
|
6
|
+
editor) composition, card anatomy, plus an aggregated token/contrast/lifecycle
|
|
7
7
|
drift battery (`npm run dogfood:status`). Use for "run a dogfood sweep",
|
|
8
8
|
"find broken demos", "audit native primitive leaks". NOT for gen-UI
|
|
9
9
|
gallery scoring (gen-ui-review) or authoring primitives (primitive-authoring).
|
|
@@ -33,7 +33,7 @@ below — load it before running or triaging.
|
|
|
33
33
|
| 2 | App-shell QA — after `apps/` structural sweeps; before a release | [app-shell-pitfalls](references/app-shell-pitfalls.md) |
|
|
34
34
|
| 3 | HTML attr-quote typo sweep — nested `"` broke an attribute boundary | [html-attr-sweep](references/html-attr-sweep.md) |
|
|
35
35
|
| 4 | Native-primitive leak — `<button>` where `<button-ui>` exists | [native-leak-annotations](references/native-leak-annotations.md) |
|
|
36
|
-
| 5 |
|
|
36
|
+
| 5 | Shell composition — incomplete `<admin-shell>` / `<chat-shell>` / `<editor-shell>` anatomy | [admin-shell-anatomy](references/admin-shell-anatomy.md), [chat-shell-anatomy](references/chat-shell-anatomy.md), [editor-shell-anatomy](references/editor-shell-anatomy.md) |
|
|
37
37
|
| 6 | Card structure + anatomy docs coverage | [card-anatomy-sweep](references/card-anatomy-sweep.md) |
|
|
38
38
|
| 7 | Token/contrast/lifecycle/yaml drift battery — independent of modes 1–6 | [mode7-status-battery](references/mode7-status-battery.md) |
|
|
39
39
|
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "Demo Audit"
|
|
3
|
-
short_description: "Seven-mode QA sweep of the monorepo's demo/app surfaces: visual probe, app-shell QA, attr-quote typos, native-primitive leak,
|
|
3
|
+
short_description: "Seven-mode QA sweep of the monorepo's demo/app surfaces: visual probe, app-shell QA, attr-quote typos, native-primitive leak, shell (admin/chat/ editor) composition, card anatomy, plus an aggregated token/contrast/lifecycle drift battery (`npm run dogfood:status`)."
|
|
@@ -4,7 +4,10 @@ Script: `node scripts/dev/audit-app-shells.mjs` (repo-local). Walks every
|
|
|
4
4
|
`apps/<name>/…/<page>.html` shell headlessly and checks console errors,
|
|
5
5
|
custom-element registration, collapsed heights, icon-ui presence, demo-root
|
|
6
6
|
flex, and network 4xx/5xx. Flags: `--only=NAME` · `--fail-fast` ·
|
|
7
|
-
`--compare-prod` (diff registered tags against the prod deploy)
|
|
7
|
+
`--compare-prod` (diff registered tags against the prod deploy) ·
|
|
8
|
+
`--playgrounds` (gh#3197 — also sweeps `playgrounds/<name>/app/<name>.html`,
|
|
9
|
+
same shell shape; opt-in because that root carries other pre-existing,
|
|
10
|
+
unaudited findings — combine with `--only=NAME` to scope to one playground).
|
|
8
11
|
|
|
9
12
|
Prerequisites: `npm run dev` running (vite `:5173`); `npm run proxy` only when
|
|
10
13
|
probing chat / gen-ui pages. If vite is mid dep-reoptimization the first sweep
|
|
@@ -42,6 +45,8 @@ with no console error, regardless of real content height.
|
|
|
42
45
|
| `button-ui` (with `icon=`) | `icon-ui` |
|
|
43
46
|
| `badge-ui` (with `icon=`) | `icon-ui` |
|
|
44
47
|
| `menu-item-ui` | `icon-ui`, `text-ui` |
|
|
48
|
+
| `admin-roster-ui` | `upload-ui`, `menu-item-ui` |
|
|
49
|
+
| `table-toolbar-ui` | `search-ui`, `select-ui`, `menu-ui`, `menu-item-ui` |
|
|
45
50
|
|
|
46
51
|
## Registration diagnosis rules
|
|
47
52
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Mode 5b — Chat-shell composition: the canonical parts
|
|
2
|
+
|
|
3
|
+
Script: `scripts/dev/audit-shell-composition.mjs` (repo-local), npm gates
|
|
4
|
+
`audit:shell-composition{,:strict,:all}`. Static AST walk over
|
|
5
|
+
`apps/**/*.html`, `playgrounds/**/*.html`, `catalog/page-shells/**/*.html` —
|
|
6
|
+
no browser needed; pre-commit fast. Shares one script and one output
|
|
7
|
+
contract with the `<admin-shell>` census
|
|
8
|
+
([admin-shell-anatomy](admin-shell-anatomy.md)) — the script's own `CHAT_PARTS`
|
|
9
|
+
array is the mechanical census; this file is the human review standard.
|
|
10
|
+
Canonical source: `packages/web-modules/chat/chat-shell/chat-shell.yaml`
|
|
11
|
+
(the behavioral contract) and `playgrounds/chat/app/chat.contents.html`
|
|
12
|
+
(the canonical rendered reference, cited by `apps/genui/PATTERNS.md`'s
|
|
13
|
+
chrome-decision table as chat-shell's canonical demo).
|
|
14
|
+
|
|
15
|
+
`gh#2909`/`apps/genui/PATTERNS.md:329-337` established that `apps/genui`
|
|
16
|
+
(gen-ui, factory-chat) is a real `<chat-shell>` consumer that a
|
|
17
|
+
`<admin-shell>`-only mode 5 sweep never scanned — this anatomy closes that
|
|
18
|
+
blind spot.
|
|
19
|
+
|
|
20
|
+
## The canonical parts
|
|
21
|
+
|
|
22
|
+
1. `<chat-shell provider="…" model="…" proxy-url="…">` outer.
|
|
23
|
+
2. `<chat-thread>` direct child — the message scroll surface. Required:
|
|
24
|
+
without it there is nowhere for the host's rendering pipeline to append
|
|
25
|
+
messages.
|
|
26
|
+
3. `<chat-thread> > <chat-empty>` as (typically first) child — the
|
|
27
|
+
empty-state placeholder shown via the `[empty]` reflected attribute
|
|
28
|
+
before any message exists.
|
|
29
|
+
4. `<chat-composer>` direct child — the input region.
|
|
30
|
+
5. `<chat-composer> > <chat-input-ui>` (or `<input-ui>`) inner child — the
|
|
31
|
+
actual input; a composer with no primitive input inside has nothing to
|
|
32
|
+
submit.
|
|
33
|
+
6. `<chat-header>` direct child — optional top chrome bar (name, status,
|
|
34
|
+
actions). When present, expected to carry `[slot="name"]` and
|
|
35
|
+
`[slot="status"]` (typically a `<chat-status>`) — a header with neither
|
|
36
|
+
is bare chrome with no identifying content.
|
|
37
|
+
7. `<chat-sidebar slot="leading"|"trailing">` — optional conversation-history
|
|
38
|
+
or inspector rail.
|
|
39
|
+
8. No generic layout primitive (`<col-ui>`, `<row-ui>`, `<stack-ui>`) as a
|
|
40
|
+
**direct child** of `<chat-shell>` — the shell's CSS lays out children by
|
|
41
|
+
tag selector (`chat-thread`, `chat-composer`, etc.); a generic wrapper
|
|
42
|
+
defeats that and the shell's `:has(chat-thread[streaming])` cross-cut
|
|
43
|
+
styling.
|
|
44
|
+
|
|
45
|
+
## Severity mapping
|
|
46
|
+
|
|
47
|
+
- **critical** — `<chat-shell>` present but missing `<chat-thread>` or
|
|
48
|
+
`<chat-composer>` (parts 2, 4). The shell can't render a usable
|
|
49
|
+
conversation surface without both.
|
|
50
|
+
- **warning** — `<chat-composer>` has no inner `<chat-input-ui>`/`<input-ui>`
|
|
51
|
+
(part 5); a `<chat-header>` present but missing both `[slot="name"]` and
|
|
52
|
+
`[slot="status"]` content (part 6); a generic layout primitive
|
|
53
|
+
(`col-ui`/`row-ui`/`stack-ui`) authored as a direct child (part 8).
|
|
54
|
+
- **info** — `<chat-thread>` missing its `<chat-empty>` first child (part 3;
|
|
55
|
+
a thread pre-seeded with real messages legitimately skips this);
|
|
56
|
+
`<chat-header>` absent entirely (part 6 is optional chrome);
|
|
57
|
+
`<chat-sidebar>` absent (part 7, forward-looking per the yaml — chat is
|
|
58
|
+
typically single-pane).
|
|
59
|
+
|
|
60
|
+
## What the script flags (mechanical subset)
|
|
61
|
+
|
|
62
|
+
| Symptom | Diagnosis |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `<chat-shell>` with no `<chat-thread>` | critical — part 2 |
|
|
65
|
+
| `<chat-shell>` with no `<chat-composer>` | critical — part 4 |
|
|
66
|
+
| `<chat-composer>` with no `<chat-input-ui>`/`<input-ui>` child | warning — part 5 |
|
|
67
|
+
| `<chat-header>` present, no `[slot="name"]` and no `[slot="status"]`/`<chat-status>` | warning — part 6 |
|
|
68
|
+
| `<chat-shell>` direct child is `col-ui`/`row-ui`/`stack-ui` | warning — part 8 (legacy-generic-layout leak) |
|
|
69
|
+
| `<chat-thread>` with no `<chat-empty>` child | info — part 3 |
|
|
70
|
+
|
|
71
|
+
## Opt-out contract
|
|
72
|
+
|
|
73
|
+
Same annotation mechanism as admin-shell: `<chat-shell
|
|
74
|
+
data-shell-opt-out="reason">` downgrades every finding on that shell to
|
|
75
|
+
info and prints the reason for reviewers.
|
|
76
|
+
|
|
77
|
+
## Triage
|
|
78
|
+
|
|
79
|
+
- Canonical product surface (`apps/genui` chat/factory-chat pages,
|
|
80
|
+
`playgrounds/chat/*`) → fix mandatory.
|
|
81
|
+
- A narrow single-feature playground isolating one chat behavior → fix
|
|
82
|
+
optional; annotate the opt-out.
|
|
83
|
+
- Never point this audit at `packages/web-modules/chat/**/*.examples.html`
|
|
84
|
+
or `packages/web-components/components/*/*.html` (single-primitive
|
|
85
|
+
spotlights) — the script's `isShowcaseDemo`/showcase-path exclusion
|
|
86
|
+
already keeps those out of scope, same as admin-shell.
|
|
87
|
+
- `:strict` is the CI/publish posture; keep warn-only while iterating
|
|
88
|
+
locally.
|
|
89
|
+
|
|
90
|
+
## Legacy shapes — never re-authored
|
|
91
|
+
|
|
92
|
+
`chat-shell.yaml`'s own description lists the ADR-0024-retired legacy
|
|
93
|
+
data-attribute shapes (`<section data-chat-messages>`, `<chat-input-ui
|
|
94
|
+
data-chat-input>`, `<empty-state-ui data-chat-empty>`, `<header
|
|
95
|
+
data-chat-name>`) as silently unrecognized, not merely deprecated. This
|
|
96
|
+
audit does not re-detect them (a separate concern from anatomy
|
|
97
|
+
completeness) — `verify:no-legacy-shell-shapes` in `npm run check` already
|
|
98
|
+
covers that ground.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Mode 5c — Editor-shell composition: the canonical parts
|
|
2
|
+
|
|
3
|
+
Script: `scripts/dev/audit-shell-composition.mjs` (repo-local), npm gates
|
|
4
|
+
`audit:shell-composition{,:strict,:all}`. Static AST walk over
|
|
5
|
+
`apps/**/*.html`, `playgrounds/**/*.html`, `catalog/page-shells/**/*.html` —
|
|
6
|
+
no browser needed; pre-commit fast. Shares one script and one output
|
|
7
|
+
contract with the `<admin-shell>` census
|
|
8
|
+
([admin-shell-anatomy](admin-shell-anatomy.md)) — the script's own
|
|
9
|
+
`EDITOR_PARTS` array is the mechanical census; this file is the human
|
|
10
|
+
review standard. Canonical source:
|
|
11
|
+
`packages/web-modules/editor/editor-shell/editor-shell.yaml` (the
|
|
12
|
+
behavioral contract) and `apps/construct-canvas/app/construct-canvas.contents.html`
|
|
13
|
+
(the canonical rendered reference, cited by `apps/genui/PATTERNS.md`'s
|
|
14
|
+
chrome-decision table as editor-shell's canonical demo, alongside
|
|
15
|
+
`apps/genui`'s own a2ui-editor consumer).
|
|
16
|
+
|
|
17
|
+
`gh#2909`/`apps/genui/PATTERNS.md:329-337` established that `apps/genui`
|
|
18
|
+
(a2ui-editor) is a real `<editor-shell>` consumer that a
|
|
19
|
+
`<admin-shell>`-only mode 5 sweep never scanned — this anatomy closes that
|
|
20
|
+
blind spot.
|
|
21
|
+
|
|
22
|
+
## The canonical parts
|
|
23
|
+
|
|
24
|
+
1. `<editor-shell>` outer.
|
|
25
|
+
2. `<editor-canvas>` direct child — the central work surface. Required:
|
|
26
|
+
without it there is nowhere for artboards/document body/canvas content
|
|
27
|
+
to land, the editor equivalent of admin-shell's `<admin-content>`.
|
|
28
|
+
3. `<editor-toolbar>` direct child — the app-scope top chrome bar (document
|
|
29
|
+
title, run/save/undo/redo, focus-mode toggle). Recommended; an editor
|
|
30
|
+
with no toolbar has no document-wide action surface.
|
|
31
|
+
4. `<editor-canvas> > <editor-canvas-empty>` as (typically first) child —
|
|
32
|
+
the empty-state placeholder shown via the parent's `[empty]` reflected
|
|
33
|
+
attribute before any content exists.
|
|
34
|
+
5. `<editor-canvas> > <editor-canvas-toolbar>` — optional canvas-scope
|
|
35
|
+
chrome (view-mode tabs, breadcrumbs) sticky to the canvas top edge;
|
|
36
|
+
distinct from part 3's app-scope toolbar.
|
|
37
|
+
6. `<editor-statusbar>` direct child — bottom chrome bar (save/sync state,
|
|
38
|
+
zoom, cursor position). Recommended; loses the canonical status-strip
|
|
39
|
+
without it.
|
|
40
|
+
7. `<editor-sidebar slot="leading"|"trailing">` — optional navigator or
|
|
41
|
+
inspector rail.
|
|
42
|
+
8. When an `<editor-sidebar>` is present, it must wrap `<pane-ui
|
|
43
|
+
resizable>` (or at minimum `<pane-ui>`) — editor-sidebar is the one
|
|
44
|
+
bespoke shell child that **delegates** rather than duplicates a
|
|
45
|
+
primitive's resize behavior (per `shell-patterns.md`'s "FIRST bespoke
|
|
46
|
+
shell child that delegates" note); an editor-sidebar with no inner
|
|
47
|
+
`<pane-ui>` reimplements drag by hand instead of reusing the primitive.
|
|
48
|
+
9. No bare `<header>` / `<footer>` native elements as direct children of
|
|
49
|
+
`<editor-shell>` — these are the ADR-0024-retired legacy chrome shapes
|
|
50
|
+
that `<editor-toolbar>` / `<editor-statusbar>` replaced.
|
|
51
|
+
|
|
52
|
+
## Severity mapping
|
|
53
|
+
|
|
54
|
+
- **critical** — `<editor-shell>` present but missing `<editor-canvas>`
|
|
55
|
+
(part 2). The shell can't render usable content without it.
|
|
56
|
+
- **warning** — no `<editor-toolbar>` (part 3); no `<editor-statusbar>`
|
|
57
|
+
(part 6); an `<editor-sidebar>` present with no inner `<pane-ui>` (part
|
|
58
|
+
8); a bare native `<header>`/`<footer>` direct child (part 9, the
|
|
59
|
+
retired-legacy-shape leak).
|
|
60
|
+
- **info** — `<editor-canvas>` missing its `<editor-canvas-empty>` first
|
|
61
|
+
child (part 4; a canvas pre-seeded with real content legitimately skips
|
|
62
|
+
this); no `<editor-canvas-toolbar>` (part 5, optional canvas chrome); no
|
|
63
|
+
`<editor-sidebar>` at all (part 7 — `construct-canvas`'s own comment
|
|
64
|
+
notes "no leading pane today", a legitimately sidebar-less composition).
|
|
65
|
+
|
|
66
|
+
## What the script flags (mechanical subset)
|
|
67
|
+
|
|
68
|
+
| Symptom | Diagnosis |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `<editor-shell>` with no `<editor-canvas>` | critical — part 2 |
|
|
71
|
+
| `<editor-shell>` with no `<editor-toolbar>` | warning — part 3 |
|
|
72
|
+
| `<editor-shell>` with no `<editor-statusbar>` | warning — part 6 |
|
|
73
|
+
| `<editor-sidebar>` present, no inner `<pane-ui>` | warning — part 8 (delegation contract violated) |
|
|
74
|
+
| `<editor-shell>` direct child is native `<header>`/`<footer>` | warning — part 9 (retired-legacy-shape leak) |
|
|
75
|
+
| `<editor-canvas>` with no `<editor-canvas-empty>` child | info — part 4 |
|
|
76
|
+
|
|
77
|
+
## Opt-out contract
|
|
78
|
+
|
|
79
|
+
Same annotation mechanism as admin-shell: `<editor-shell
|
|
80
|
+
data-shell-opt-out="reason">` downgrades every finding on that shell to
|
|
81
|
+
info and prints the reason for reviewers.
|
|
82
|
+
|
|
83
|
+
## Triage
|
|
84
|
+
|
|
85
|
+
- Canonical product surface (`apps/construct-canvas`, `apps/genui`'s
|
|
86
|
+
a2ui-editor) → fix mandatory.
|
|
87
|
+
- A narrow single-feature playground isolating one editor behavior → fix
|
|
88
|
+
optional; annotate the opt-out.
|
|
89
|
+
- Never point this audit at `packages/web-modules/editor/**/*.examples.html`
|
|
90
|
+
or `packages/web-components/components/*/*.html` (single-primitive
|
|
91
|
+
spotlights) — the script's `isShowcaseDemo`/showcase-path exclusion
|
|
92
|
+
already keeps those out of scope, same as admin-shell.
|
|
93
|
+
- **Don't nest `<editor-shell>` inside `<admin-shell>`** as page chrome —
|
|
94
|
+
`editor-shell.examples.html` documents them as sibling surfaces, not
|
|
95
|
+
nested; this audit doesn't mechanically flag the nesting mistake (a
|
|
96
|
+
cross-shell structural rule, not a within-shell anatomy gap), but a
|
|
97
|
+
reviewer seeing both tags in one file should treat it as a design smell.
|
|
98
|
+
- `:strict` is the CI/publish posture; keep warn-only while iterating
|
|
99
|
+
locally.
|
|
100
|
+
|
|
101
|
+
## Legacy shapes — never re-authored
|
|
102
|
+
|
|
103
|
+
`editor-shell.yaml`'s own description lists the ADR-0024-retired legacy
|
|
104
|
+
data-attribute shapes (`<header>`, `<div data-editor-body>`, `<pane-ui
|
|
105
|
+
data-left|data-right>`, `<div data-canvas>`, `<footer>`, `<span
|
|
106
|
+
data-spacer>`) as silently unrecognized, not merely deprecated. Part 9
|
|
107
|
+
above catches the two structural container tags (`<header>`/`<footer>`)
|
|
108
|
+
mechanically; the finer-grained data-attribute forms are already covered
|
|
109
|
+
by `verify:no-legacy-shell-shapes` in `npm run check`.
|
|
@@ -37,6 +37,16 @@ import { chromium } from 'playwright';
|
|
|
37
37
|
import { mkdir, writeFile, readFile } from 'node:fs/promises';
|
|
38
38
|
import { existsSync } from 'node:fs';
|
|
39
39
|
import { join } from 'node:path';
|
|
40
|
+
// gh#3616: the shared overflow predicate, vendored (not import()able:
|
|
41
|
+
// this script ships inside a published plugin package with no apps/ tree
|
|
42
|
+
// in its files allowlist, see this repo's scripts/build/
|
|
43
|
+
// overflow-detect-vendor.mjs for the derive-and-vendor build that keeps
|
|
44
|
+
// this copy provably identical to apps/genui/app/_shared/overflow-detect.js).
|
|
45
|
+
// Imported as an ordinary function and passed BY REFERENCE into
|
|
46
|
+
// elementHandle.evaluate() below, which stringifies it into the browser's
|
|
47
|
+
// page context, same mechanism scripts/qa/gen-review-decompose.mjs's own
|
|
48
|
+
// (non-vendored, import()-able) copy uses.
|
|
49
|
+
import { OVERFLOW_TAGS, detectOverflow } from './overflow-detect.generated.mjs';
|
|
40
50
|
|
|
41
51
|
// The script lives inside the plugin but runs against the monorepo. All
|
|
42
52
|
// monorepo paths are resolved from the working directory (the monorepo root),
|
|
@@ -259,7 +269,7 @@ for (const group of gallery.groups) {
|
|
|
259
269
|
for (const engineKey of Object.keys(prompt.engines ?? {})) {
|
|
260
270
|
const engineData = prompt.engines[engineKey];
|
|
261
271
|
if (!engineData || engineData.dryRun) continue;
|
|
262
|
-
allPrompts.push({ group: group.slug, prompt: prompt.slug, engineKey, engineData });
|
|
272
|
+
allPrompts.push({ group: group.slug, prompt: prompt.slug, label: prompt.label, engineKey, engineData });
|
|
263
273
|
}
|
|
264
274
|
}
|
|
265
275
|
}
|
|
@@ -321,7 +331,7 @@ const results = [];
|
|
|
321
331
|
let renderFailureCount = 0;
|
|
322
332
|
let idx = 0;
|
|
323
333
|
|
|
324
|
-
for (const { group, prompt: promptSlug, engineKey, engineData } of allPrompts) {
|
|
334
|
+
for (const { group, prompt: promptSlug, label, engineKey, engineData } of allPrompts) {
|
|
325
335
|
idx++;
|
|
326
336
|
const slug = `${group}-${promptSlug}-${engineKey}`;
|
|
327
337
|
process.stdout.write(` [${idx}/${allPrompts.length}] ${slug.padEnd(50)} `);
|
|
@@ -331,7 +341,10 @@ for (const { group, prompt: promptSlug, engineKey, engineData } of allPrompts) {
|
|
|
331
341
|
// Each has a .gallery-canvas-wrap containing a canvas-ui.
|
|
332
342
|
// We identify by matching the group anchor + prompt h3 text.
|
|
333
343
|
|
|
334
|
-
|
|
344
|
+
// gh#3249: use the gallery's real prompt.label (what the h3 actually renders), not a
|
|
345
|
+
// title-cased guess derived from the slug -- the two diverge whenever the authored label
|
|
346
|
+
// isn't the slug's mechanical title-case ("search-filters" vs "Search with Filters").
|
|
347
|
+
const promptLabel = label;
|
|
335
348
|
|
|
336
349
|
// Scroll to the prompt section and wait for canvas settle
|
|
337
350
|
await page.evaluate(({ groupSlug, label }) => {
|
|
@@ -407,41 +420,21 @@ for (const { group, prompt: promptSlug, engineKey, engineData } of allPrompts) {
|
|
|
407
420
|
// canvas. Runs only when the canvas rendered (renderFailure = false).
|
|
408
421
|
// Results land in decomposed.json as `overflowElements` — a non-empty array
|
|
409
422
|
// is treated as P1 in Phase 4 regardless of the Phase 3 structural score.
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
423
|
+
//
|
|
424
|
+
// gh#3616: the predicate itself (both the TEXT-truncation and LAYOUT-
|
|
425
|
+
// overflow checks, plus every documented exclusion: hscroll boundary,
|
|
426
|
+
// <svg> subtree) now lives in overflow-detect.generated.mjs (vendored
|
|
427
|
+
// from apps/genui/app/_shared/overflow-detect.js), passed straight into
|
|
428
|
+
// elementHandle.evaluate() below rather than re-implemented here, so
|
|
429
|
+
// this plugin sweep and the in-repo QA script/live artifact pane provably
|
|
430
|
+
// run the same code. `canvasWrapEl` is the exact same `.gallery-canvas-
|
|
431
|
+
// wrap` element the screenshot/render-failure check above already
|
|
432
|
+
// resolved, so there's no need to re-locate it by groupSlug/label.
|
|
415
433
|
|
|
416
434
|
let overflowElements = [];
|
|
417
435
|
|
|
418
436
|
if (!renderFailure) {
|
|
419
|
-
overflowElements = await
|
|
420
|
-
const section = document.getElementById(`group-${groupSlug}`);
|
|
421
|
-
if (!section) return [];
|
|
422
|
-
const h3s = [...section.querySelectorAll('.gallery-prompt-heading')];
|
|
423
|
-
const h3 = h3s.find(h => h.textContent.trim().toLowerCase() === label.toLowerCase());
|
|
424
|
-
const wrap = h3?.closest('.gallery-prompt')?.querySelector('.gallery-canvas-wrap');
|
|
425
|
-
if (!wrap) return [];
|
|
426
|
-
|
|
427
|
-
const found = [];
|
|
428
|
-
function walk(el) {
|
|
429
|
-
const tag = el.tagName?.toLowerCase() ?? '';
|
|
430
|
-
if (overflowTags.includes(tag)) {
|
|
431
|
-
const style = getComputedStyle(el);
|
|
432
|
-
const hasHidden = style.overflow === 'hidden' || style.overflowX === 'hidden';
|
|
433
|
-
if (hasHidden && el.scrollWidth > el.clientWidth + 2) {
|
|
434
|
-
found.push({ tag, clippedWidth: true });
|
|
435
|
-
}
|
|
436
|
-
if (hasHidden && el.scrollHeight > el.clientHeight + 2) {
|
|
437
|
-
found.push({ tag, clippedHeight: true });
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
for (const child of el.children) walk(child);
|
|
441
|
-
}
|
|
442
|
-
walk(wrap);
|
|
443
|
-
return found;
|
|
444
|
-
}, { groupSlug: group, label: promptLabel, overflowTags: [...OVERFLOW_TAGS] });
|
|
437
|
+
overflowElements = await canvasWrapEl.evaluate(detectOverflow, OVERFLOW_TAGS);
|
|
445
438
|
}
|
|
446
439
|
|
|
447
440
|
// ── Primitive lookup + sanitize ──────────────────────────────────────────
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// GENERATED, do not hand-edit. Source: apps/genui/app/_shared/overflow-detect.js
|
|
2
|
+
// Rebuild: node scripts/build/overflow-detect-vendor.mjs
|
|
3
|
+
// Freshness gate: node scripts/build/overflow-detect-vendor.mjs --verify (npm run check:overflow-detect-vendor-fresh)
|
|
4
|
+
//
|
|
5
|
+
// gh#3616: vendored so the gen-ui-review plugin's own decompose script (a
|
|
6
|
+
// published package with no apps/ tree in its files allowlist) runs the
|
|
7
|
+
// EXACT SAME overflow/clip predicate as the in-repo QA script and the live
|
|
8
|
+
// artifact pane, instead of a hand-kept inline copy that can silently drift
|
|
9
|
+
// (the gap gh#3616 closed, see that ticket for the pre-fix divergence).
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Tags the text-truncation check inspects for their own `overflow:hidden`
|
|
13
|
+
* clipping a2ui-root/canvas-ui render. Every other tag is still walked for
|
|
14
|
+
* the layout-overflow check below, which applies to any tag.
|
|
15
|
+
*/
|
|
16
|
+
export const OVERFLOW_TAGS = [
|
|
17
|
+
'text-ui', 'stat-ui', 'badge-ui', 'field-ui', 'button-ui',
|
|
18
|
+
'label', 'span', 'p', 'h1', 'h2', 'h3', 'h4',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Walks a rendered container and reports overflow/clip findings.
|
|
23
|
+
*
|
|
24
|
+
* Two independent clipping mechanisms (gh#3248):
|
|
25
|
+
* - TEXT truncation: an element's own `overflow:hidden` clips its own
|
|
26
|
+
* scrollWidth/scrollHeight (e.g. a Text node with no line-clamp room).
|
|
27
|
+
* Checked only for `overflowTags` elements.
|
|
28
|
+
* - LAYOUT overflow: an element's whole box lays out past the
|
|
29
|
+
* container's edge, whether or not anything in its ancestor chain has
|
|
30
|
+
* `overflow:hidden` (e.g. two sibling `[stretch]` buttons in a Row
|
|
31
|
+
* each claiming full row width, one landing off-canvas). Checked for
|
|
32
|
+
* every visible tag, horizontal only. Vertical overflow is never
|
|
33
|
+
* flagged since the container is expected to scroll vertically (the
|
|
34
|
+
* gallery's own `.gallery-canvas-wrap` is deliberately
|
|
35
|
+
* `overflow-y: auto`), and a container never scrolls sideways so a
|
|
36
|
+
* right-edge slice is always wrong.
|
|
37
|
+
*
|
|
38
|
+
* Both checks skip content inside a genuine horizontal-scroll boundary
|
|
39
|
+
* (table-ui's own scroll wrapper, swiper-ui, tabs-ui's overflow-x strip):
|
|
40
|
+
* content past the visible edge there is intentional, scrolled-to
|
|
41
|
+
* content, not a layout bug. Also skipped: content inside an `<svg>`
|
|
42
|
+
* subtree (icon-ui's glyph markup, chart-ui's rendered marks), since SVG
|
|
43
|
+
* coordinate-space geometry doesn't map onto DOM layout-box geometry the
|
|
44
|
+
* same way, confirmed via a live false positive on chart-ui's own
|
|
45
|
+
* sparkline. `<foreignObject>` re-enters normal HTML flow content inside
|
|
46
|
+
* that `<svg>` subtree (SVG spec), so the svg-exclusion resets for its
|
|
47
|
+
* own children rather than staying latched: a real HTML element nested
|
|
48
|
+
* there gets checked again.
|
|
49
|
+
*
|
|
50
|
+
* `container` itself is excluded from the horizontal-scroll-boundary test:
|
|
51
|
+
* per the CSS Overflow spec, pairing `overflow-y: auto` with the default
|
|
52
|
+
* `overflow-x: visible` computes overflow-x to `auto` too. Treating that
|
|
53
|
+
* as a real scroll boundary would mark every one of the container's own
|
|
54
|
+
* children as inside-hscroll and silently disable the layout-overflow
|
|
55
|
+
* check for the entire canvas, the opposite of what it exists to catch.
|
|
56
|
+
*
|
|
57
|
+
* @param {Element} container the rendered surface's outer boundary
|
|
58
|
+
* (gen-review-decompose.mjs's `.gallery-canvas-wrap`; the live artifact
|
|
59
|
+
* pane's own equivalent container).
|
|
60
|
+
* @param {string[]} overflowTags see OVERFLOW_TAGS; always pass this
|
|
61
|
+
* explicitly (see the module-doc note on why there's no default value).
|
|
62
|
+
* @returns {Array<{tag: string, clippedWidth?: true, clippedHeight?: true, pushedPastContainer?: true}>}
|
|
63
|
+
*/
|
|
64
|
+
export function detectOverflow(container, overflowTags) {
|
|
65
|
+
const found = [];
|
|
66
|
+
const containerRect = container.getBoundingClientRect();
|
|
67
|
+
const TOLERANCE = 2; // matches the scrollWidth/clientWidth +2 slack below
|
|
68
|
+
|
|
69
|
+
function walk(el, insideHScroll, isContainer, insideSvg) {
|
|
70
|
+
const tag = el.tagName?.toLowerCase() ?? '';
|
|
71
|
+
const style = getComputedStyle(el);
|
|
72
|
+
|
|
73
|
+
if (overflowTags.includes(tag)) {
|
|
74
|
+
const hasHidden = style.overflow === 'hidden' || style.overflowX === 'hidden';
|
|
75
|
+
if (hasHidden && el.scrollWidth > el.clientWidth + TOLERANCE) {
|
|
76
|
+
found.push({ tag, clippedWidth: true });
|
|
77
|
+
}
|
|
78
|
+
if (hasHidden && el.scrollHeight > el.clientHeight + TOLERANCE) {
|
|
79
|
+
found.push({ tag, clippedHeight: true });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!insideHScroll && !insideSvg) {
|
|
84
|
+
const rect = el.getBoundingClientRect();
|
|
85
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
86
|
+
if (rect.right > containerRect.right + TOLERANCE || rect.left < containerRect.left - TOLERANCE) {
|
|
87
|
+
found.push({ tag, pushedPastContainer: true });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const isHScrollRoot = !isContainer
|
|
93
|
+
&& (style.overflowX === 'auto' || style.overflowX === 'scroll')
|
|
94
|
+
&& el.scrollWidth > el.clientWidth + TOLERANCE;
|
|
95
|
+
const nowInsideSvg = tag === 'foreignobject' ? false : (insideSvg || tag === 'svg');
|
|
96
|
+
for (const child of el.children) walk(child, insideHScroll || isHScrollRoot, false, nowInsideSvg);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
walk(container, false, true, false);
|
|
100
|
+
return found;
|
|
101
|
+
}
|