@godxjp/ui 32.9.0 → 32.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DESIGN.md +2 -1
- package/agent/DESIGN.md +2 -1
- package/agent/START-HERE.md +1 -1
- package/agent/components/Progress.json +3 -3
- package/agent/components/Tree.json +1 -0
- package/agent/components.json +4 -3
- package/agent/index.json +2 -2
- package/agent/llms.txt +3 -3
- package/agent/tokens.json +1 -1
- package/dist/components/data-display/progress.d.ts +12 -6
- package/dist/components/data-display/progress.js +1 -1
- package/dist/components/data-display/tree.js +39 -0
- package/dist/components/layout/breadcrumb.js +2 -2
- package/dist/components/layout/page-container.js +2 -2
- package/dist/contracts/measurement.json +1 -1
- package/dist/styles/data-display-layout.css +18 -2
- package/dist/styles/dialog-layout.css +2 -0
- package/dist/styles/layers.json +1 -1
- package/dist/styles/navigation-layout.css +3 -0
- package/dist/styles.css +1 -1
- package/package.json +2 -2
- package/scripts/cli.mjs +5 -2
- package/scripts/visual-audit.mjs +55 -12
package/package.json
CHANGED
package/scripts/cli.mjs
CHANGED
|
@@ -85,14 +85,17 @@ const HELP = {
|
|
|
85
85
|
--dry-run list every change without writing
|
|
86
86
|
What it cannot decide (a component move that changes props, an AppProvider that relied on the
|
|
87
87
|
old vi default) is printed as a "note" for you to finish.`,
|
|
88
|
-
"visual-audit": `godxjp-ui visual-audit [--format json] [--strict] [--widths WxH,…] <baseUrl> [route …]
|
|
88
|
+
"visual-audit": `godxjp-ui visual-audit [--format json] [--strict] [--widths WxH,…] [--storage-state FILE] <baseUrl> [route …]
|
|
89
89
|
|
|
90
90
|
Runtime audit (Playwright) against an app you are ALREADY running locally, at six widths.
|
|
91
91
|
baseUrl the running app, e.g. http://localhost:5173
|
|
92
92
|
route … paths appended to baseUrl (default "/")
|
|
93
93
|
--widths viewports to audit (default 320x900,390x844,768x1024,1024x900,1280x900,844x390;
|
|
94
94
|
a bare width uses height 900)
|
|
95
|
-
--
|
|
95
|
+
--storage-state a Playwright storageState JSON (cookies + localStorage) so signed-in routes
|
|
96
|
+
are audited signed in; save one with
|
|
97
|
+
npx playwright codegen --save-storage=state.json <url>
|
|
98
|
+
--strict exit non-zero on any finding, not only errors
|
|
96
99
|
--format json machine-readable: findings (viewport, element, threshold + origin), unmapped
|
|
97
100
|
items (reviewer required), per-width rule status + reviewer checklist
|
|
98
101
|
--rules print the visual rule catalog as JSON and exit
|
package/scripts/visual-audit.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* godxjp-ui VISUAL audit — the runtime counterpart to the static ui-audit.mjs. Static audit
|
|
4
4
|
* (ui-audit.mjs) reads SOURCE with regexes — zero deps, fast, runs on every save.
|
|
5
5
|
*/
|
|
6
|
-
import { realpathSync } from "node:fs";
|
|
6
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import {
|
|
9
9
|
VISUAL_RULES,
|
|
@@ -212,6 +212,11 @@ export function collectInPage() {
|
|
|
212
212
|
"[role=alert], [role=status], [data-slot=alert], [class*=banner], [class*=notification]",
|
|
213
213
|
)) {
|
|
214
214
|
if (!visible(el)) continue;
|
|
215
|
+
// A screen-reader live region (`span.sr-only[role=status]`, 1x1 and clipped) is not a banner:
|
|
216
|
+
// nothing is painted, so there is no anatomy to get wrong (gh#1307 — BlockEditor's own status
|
|
217
|
+
// region was reported as "controls stacked vertically"). Its own box OR an ancestor's clip.
|
|
218
|
+
const own = el.getBoundingClientRect();
|
|
219
|
+
if (own.width <= 1 || own.height <= 1 || clippedAway(el)) continue;
|
|
215
220
|
// The kit's EmptyState is a polite status region, not a banner: it has no Alert anatomy to get
|
|
216
221
|
// wrong, so the banner checks do not apply to it (gh#1198).
|
|
217
222
|
if (el.closest("[data-slot=empty-state]")) continue;
|
|
@@ -263,7 +268,11 @@ export function collectInPage() {
|
|
|
263
268
|
return { layer, ok: v !== bad && v !== "" };
|
|
264
269
|
};
|
|
265
270
|
const layers = [
|
|
266
|
-
|
|
271
|
+
// gh#1307 — this read `borderRadius` on a bare `.ui-button`, and no rule in control.css gives
|
|
272
|
+
// `.ui-button` a radius (the corner comes from the variant classes), so the probe read 0px
|
|
273
|
+
// with the layer loaded and reported it missing on every consumer page. `display: inline-flex`
|
|
274
|
+
// is what the bare `.ui-button` rule itself declares; a div without the layer stays `block`.
|
|
275
|
+
probe("control", "ui-button", "display", "block"),
|
|
267
276
|
probe("navigation-layout", "ui-dropdown-menu-content", "borderTopWidth", "0px"),
|
|
268
277
|
// `.ui-card` is NOT a class the library sets rules on — Card renders `cn("group/card", …)`
|
|
269
278
|
// and card-layout.css owns `.ui-card-bar` / `.ui-card-cover` / `.ui-card-inset*` /
|
|
@@ -490,7 +499,7 @@ export const PROBE_RULES = [
|
|
|
490
499
|
category: "layout",
|
|
491
500
|
standard: `toggle centre within 8px of the declared rail edge at ≥1024px (${RC})`,
|
|
492
501
|
kind: "probe",
|
|
493
|
-
fix: "An edge-mounted rail toggle sits on the rail edge it controls in both desktop states. Declare the pattern with data-audit-role=rail-toggle (+ data-audit-edge=start|end).",
|
|
502
|
+
fix: "An edge-mounted rail toggle sits on the rail edge it controls in both desktop states. Declare the pattern with data-audit-role=rail-toggle (+ data-audit-edge=start|end). A toggle inside `Sidebar action` is the supported placement and reports not-applicable-sidebar-action.",
|
|
494
503
|
},
|
|
495
504
|
{
|
|
496
505
|
id: "c11-search-keyline",
|
|
@@ -638,7 +647,8 @@ export function isTrackedUppercaseLabel(l) {
|
|
|
638
647
|
*
|
|
639
648
|
* Status per rule: fail | pass | partial (mapped elements pass, but unmapped candidates remain) |
|
|
640
649
|
* unmapped (only candidates) | not-found (nothing to measure — the reviewer confirms N/A) |
|
|
641
|
-
* not-applicable-at-width |
|
|
650
|
+
* not-applicable-at-width | not-applicable-end-aligned (C11) | not-applicable-sidebar-action (C10) |
|
|
651
|
+
* checklist. Only "pass" is a machine pass, and only for what was mapped.
|
|
642
652
|
*/
|
|
643
653
|
export function evaluateProbes(raw, { url, viewport }) {
|
|
644
654
|
const T = PROBE_THRESHOLDS;
|
|
@@ -825,6 +835,8 @@ export function evaluateProbes(raw, { url, viewport }) {
|
|
|
825
835
|
);
|
|
826
836
|
noteUnmapped("c10-rail-toggle-attached", raw.c10?.candidates);
|
|
827
837
|
settle("c10-rail-toggle-attached", toggles.length);
|
|
838
|
+
if (rules["c10-rail-toggle-attached"] === "not-found" && raw.c10?.sidebarAction?.length)
|
|
839
|
+
rules["c10-rail-toggle-attached"] = "not-applicable-sidebar-action";
|
|
828
840
|
|
|
829
841
|
// C11 — desktop search keyline.
|
|
830
842
|
const keylines = applies("c11-search-keyline") ? raw.c11?.mapped || [] : [];
|
|
@@ -1117,7 +1129,13 @@ export function collectProbes(opts) {
|
|
|
1117
1129
|
reason:
|
|
1118
1130
|
"looks like a search field but carries no search mapping (SearchInput, role=searchbox, type=search or data-audit-role=search)",
|
|
1119
1131
|
}));
|
|
1120
|
-
|
|
1132
|
+
// The kit's own TopbarSearch folds to its trigger below `collapseBelow`; the consumer cannot
|
|
1133
|
+
// annotate that internal button, so it is mapped through its own hook (gh#1307).
|
|
1134
|
+
const searchTriggers = [
|
|
1135
|
+
...document.querySelectorAll(
|
|
1136
|
+
"[data-audit-role=search-trigger], [data-slot=topbar-search] .ui-topbar-search-trigger",
|
|
1137
|
+
),
|
|
1138
|
+
]
|
|
1121
1139
|
.filter(shown)
|
|
1122
1140
|
.map((el) => ({ element: desc(el), probe: tag(el) }));
|
|
1123
1141
|
|
|
@@ -1298,6 +1316,7 @@ export function collectProbes(opts) {
|
|
|
1298
1316
|
// ── C10: edge-mounted rail toggle ───────────────────────────────────────────────────────
|
|
1299
1317
|
const toggles = [];
|
|
1300
1318
|
const railCandidates = [];
|
|
1319
|
+
const sidebarActionToggles = [];
|
|
1301
1320
|
if (width >= DESKTOP) {
|
|
1302
1321
|
const rail = [
|
|
1303
1322
|
...document.querySelectorAll("[data-audit-role=rail], .app-sidebar, .sb-root"),
|
|
@@ -1326,7 +1345,11 @@ export function collectProbes(opts) {
|
|
|
1326
1345
|
for (const t of document.querySelectorAll("[aria-controls]")) {
|
|
1327
1346
|
if (t.matches("[data-audit-role=rail-toggle]") || !shown(t) || !sidebar) continue;
|
|
1328
1347
|
const target = document.getElementById(t.getAttribute("aria-controls"));
|
|
1329
|
-
if (target
|
|
1348
|
+
if (!target || !sidebar.contains(target)) continue;
|
|
1349
|
+
// `Sidebar action` is the placement DESIGN.md prescribes for a collapse toggle — inside the
|
|
1350
|
+
// rail, so the edge-mounted pattern C10 measures does not apply to it (gh#1307).
|
|
1351
|
+
if (t.closest("[data-slot=sidebar-action]")) sidebarActionToggles.push({ element: desc(t) });
|
|
1352
|
+
else
|
|
1330
1353
|
railCandidates.push({
|
|
1331
1354
|
element: desc(t),
|
|
1332
1355
|
reason:
|
|
@@ -1439,7 +1462,7 @@ export function collectProbes(opts) {
|
|
|
1439
1462
|
c04: { mapped: accounts, candidates: accountCandidates },
|
|
1440
1463
|
c05: { mapped: editors },
|
|
1441
1464
|
c06: { mapped: groups, candidates: actionCandidates },
|
|
1442
|
-
c10: { mapped: toggles, candidates: railCandidates },
|
|
1465
|
+
c10: { mapped: toggles, candidates: railCandidates, sidebarAction: sidebarActionToggles },
|
|
1443
1466
|
c11: { mapped: keylines, candidates: keylineCandidates, endAligned },
|
|
1444
1467
|
c12: { mapped: labels, candidates: labelCandidates },
|
|
1445
1468
|
};
|
|
@@ -1547,7 +1570,7 @@ async function loadDeps() {
|
|
|
1547
1570
|
* (page/context/browser) is guaranteed via finally on BOTH success and failure.
|
|
1548
1571
|
*/
|
|
1549
1572
|
/* c8 ignore start — the browser-driving glue; exercised by scripts/visual-audit-smoke.mjs in CI. */
|
|
1550
|
-
async function audit(targets, { chromium }, viewports = DEFAULT_VIEWPORTS) {
|
|
1573
|
+
async function audit(targets, { chromium }, viewports = DEFAULT_VIEWPORTS, { storageState } = {}) {
|
|
1551
1574
|
const findings = [];
|
|
1552
1575
|
const errors = [];
|
|
1553
1576
|
const matrix = [];
|
|
@@ -1568,7 +1591,7 @@ async function audit(targets, { chromium }, viewports = DEFAULT_VIEWPORTS) {
|
|
|
1568
1591
|
"(or set PLAYWRIGHT_CHROMIUM_EXECUTABLE to a system Chromium).";
|
|
1569
1592
|
throw err;
|
|
1570
1593
|
}
|
|
1571
|
-
context = await browser.newContext({ viewport: viewports[0] });
|
|
1594
|
+
context = await browser.newContext({ viewport: viewports[0], storageState });
|
|
1572
1595
|
const page = await context.newPage();
|
|
1573
1596
|
|
|
1574
1597
|
for (const url of targets) {
|
|
@@ -1813,12 +1836,32 @@ async function main() {
|
|
|
1813
1836
|
return;
|
|
1814
1837
|
}
|
|
1815
1838
|
|
|
1839
|
+
// `--storage-state <file>` — a Playwright storageState JSON (cookies + localStorage), so a
|
|
1840
|
+
// signed-in route is audited signed in (gh#1307: the consumer had to patch a copy to get past
|
|
1841
|
+
// its login). Create one with `npx playwright codegen --save-storage=state.json <url>`.
|
|
1842
|
+
const ssIdx = args.indexOf("--storage-state");
|
|
1843
|
+
const storageState = ssIdx === -1 ? undefined : args[ssIdx + 1];
|
|
1844
|
+
if (ssIdx !== -1 && (!storageState || !existsSync(storageState))) {
|
|
1845
|
+
finish(
|
|
1846
|
+
buildBootstrapError(
|
|
1847
|
+
`--storage-state: "${storageState ?? ""}" is not a readable file`,
|
|
1848
|
+
"pass a Playwright storageState JSON, e.g. from `npx playwright codegen --save-storage=state.json <url>`",
|
|
1849
|
+
),
|
|
1850
|
+
{ asJson, strict, pages: 0 },
|
|
1851
|
+
);
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1816
1855
|
const urls = args.filter(
|
|
1817
|
-
(a, i) =>
|
|
1856
|
+
(a, i) =>
|
|
1857
|
+
!a.startsWith("--") &&
|
|
1858
|
+
args[i - 1] !== "--format" &&
|
|
1859
|
+
args[i - 1] !== "--widths" &&
|
|
1860
|
+
args[i - 1] !== "--storage-state",
|
|
1818
1861
|
);
|
|
1819
1862
|
if (urls.length === 0) {
|
|
1820
1863
|
const msg =
|
|
1821
|
-
"no baseUrl given — usage: visual-audit.mjs [--format json] [--strict] [--widths 390x844,1280x900] <baseUrl> [route …] " +
|
|
1864
|
+
"no baseUrl given — usage: visual-audit.mjs [--format json] [--strict] [--widths 390x844,1280x900] [--storage-state state.json] <baseUrl> [route …] " +
|
|
1822
1865
|
"(baseUrl is the running app; extra args are routes appended to it)";
|
|
1823
1866
|
finish(buildBootstrapError(msg), { asJson, strict, pages: 0 });
|
|
1824
1867
|
return;
|
|
@@ -1839,7 +1882,7 @@ async function main() {
|
|
|
1839
1882
|
}
|
|
1840
1883
|
|
|
1841
1884
|
try {
|
|
1842
|
-
const result = await audit(targets, deps, viewports);
|
|
1885
|
+
const result = await audit(targets, deps, viewports, { storageState });
|
|
1843
1886
|
finish(result, { asJson, strict, pages: targets.length });
|
|
1844
1887
|
} catch (e) {
|
|
1845
1888
|
// A launch/setup failure = infrastructure error (never "zero violations").
|