@bacnh85/pi-ux 0.4.6 → 0.4.7
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/CHANGELOG.md +16 -0
- package/README.md +1 -1
- package/extensions/index.js +15 -2
- package/hooks/ux-audit.js +2 -1
- package/package.json +1 -1
- package/skills/ux-capture/SKILL.md +12 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.7 (2026-09-12)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- `ux_audit` states hints now match the actual failure. With interactive
|
|
8
|
+
selectors present but focus/disabled rules failing (the fragment case 0.4.6
|
|
9
|
+
promised to catch), the result appends "states rules may live in another
|
|
10
|
+
file — pass the COMPLETE stylesheet" (previously: no hint at all). With zero
|
|
11
|
+
interactive selectors the only possible failure is reduced-motion, so the
|
|
12
|
+
hint names that fix and only conditionally suggests the fragment case
|
|
13
|
+
instead of assertively mislabeling a complete stylesheet.
|
|
14
|
+
(`scanStates` reports `hasInteractive`; `formatAuditResult` is now exported
|
|
15
|
+
and covered by a test.)
|
|
16
|
+
- README Usage: bare `/ux` resets to the configured default mode (it never
|
|
17
|
+
toggled).
|
|
18
|
+
|
|
3
19
|
## 0.4.6 (2026-09-06)
|
|
4
20
|
|
|
5
21
|
### Added
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ Pi auto-discovers the extension and skill.
|
|
|
19
19
|
## Usage
|
|
20
20
|
|
|
21
21
|
```
|
|
22
|
-
/ux #
|
|
22
|
+
/ux # reset to configured default mode
|
|
23
23
|
/ux strict # guardrail + enforce ux_audit gate before handoff (default)
|
|
24
24
|
/ux lite # guardrail only (ideation, exploration)
|
|
25
25
|
/ux off # disable
|
package/extensions/index.js
CHANGED
|
@@ -38,7 +38,9 @@ export function parseUxCommand(text, defaultMode = DEFAULT_MODE) {
|
|
|
38
38
|
const normalizedText = String(text || "").trim().toLowerCase();
|
|
39
39
|
|
|
40
40
|
if (!normalizedText) {
|
|
41
|
-
|
|
41
|
+
// Reset to the configured default, whatever it is — a deliberate
|
|
42
|
+
// `/ux default off` must survive bare `/ux` too.
|
|
43
|
+
return { type: "set-mode", mode: fallback };
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
const [primary, secondary] = normalizedText.split(/\s+/);
|
|
@@ -90,7 +92,7 @@ function auditParametersSchema() {
|
|
|
90
92
|
};
|
|
91
93
|
}
|
|
92
94
|
|
|
93
|
-
function formatAuditResult(result) {
|
|
95
|
+
export function formatAuditResult(result) {
|
|
94
96
|
const lines = [];
|
|
95
97
|
lines.push(result.pass ? "✅ UX AUDIT PASSED" : "❌ UX AUDIT FAILED");
|
|
96
98
|
lines.push("");
|
|
@@ -113,6 +115,17 @@ function formatAuditResult(result) {
|
|
|
113
115
|
for (const m of s.missingFocusVisible) lines.push(` ✗ ${m}`);
|
|
114
116
|
for (const m of s.missingDisabled) lines.push(` ✗ ${m}`);
|
|
115
117
|
for (const m of s.missingReducedMotion || []) lines.push(` ✗ ${m}`);
|
|
118
|
+
if (!s.pass) {
|
|
119
|
+
if (s.missingFocusVisible.length || s.missingDisabled.length) {
|
|
120
|
+
// Interactive selectors ARE present, yet focus/disabled rules failed —
|
|
121
|
+
// for a fragment those rules may simply live in another file.
|
|
122
|
+
lines.push(" ℹ If this is a fragment, states rules may live in another file — pass the COMPLETE stylesheet.");
|
|
123
|
+
} else if (s.hasInteractive === false) {
|
|
124
|
+
// No interactive selectors: the only possible failure is reduced-motion.
|
|
125
|
+
// Say how to fix it, and only conditionally suggest the fragment case.
|
|
126
|
+
lines.push(" ℹ Motion needs a prefers-reduced-motion fallback. If this is the complete stylesheet, add one; if it is a fragment, audit the COMPLETE stylesheet.");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
116
129
|
|
|
117
130
|
const st = result.gates.slopTells;
|
|
118
131
|
lines.push(st.pass ? "✓ Slop tells" : "✗ Slop tells");
|
package/hooks/ux-audit.js
CHANGED
|
@@ -206,7 +206,7 @@ function scanOffSystem(css, tokens) {
|
|
|
206
206
|
// .test() flake across calls, so use plain includes().
|
|
207
207
|
function scanStates(css) {
|
|
208
208
|
css = css.replace(/\/\*[\s\S]*?\*\//g, ' '); // dead code must not fail the gate
|
|
209
|
-
const findings = { missingFocusVisible: [], missingDisabled: [], missingReducedMotion: [] };
|
|
209
|
+
const findings = { missingFocusVisible: [], missingDisabled: [], missingReducedMotion: [], hasInteractive: false };
|
|
210
210
|
|
|
211
211
|
// Motion needs a reduced-motion fallback regardless of interactive elements
|
|
212
212
|
// (a hero fade-in on a page with no buttons still needs one).
|
|
@@ -219,6 +219,7 @@ function scanStates(css) {
|
|
|
219
219
|
const hasInteractive =
|
|
220
220
|
/\b(?:button|a|input|select|textarea)\b/i.test(css) || /\[role\s*=\s*"?button"?\]/i.test(css);
|
|
221
221
|
if (!hasInteractive) return findings;
|
|
222
|
+
findings.hasInteractive = true;
|
|
222
223
|
|
|
223
224
|
if (!css.includes(':focus-visible')) findings.missingFocusVisible.push('no :focus-visible rule for interactive elements');
|
|
224
225
|
if (!css.includes(':disabled')) findings.missingDisabled.push('no :disabled rule for interactive elements');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bacnh85/pi-ux",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "Anti-slop UI/UX design discipline for your Pi agent — anchors a lintable DESIGN.md, runs deterministic slop-audit gates (APCA contrast + tokens + states + slop tells), works with text-only models, ships reference design-system presets.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -12,7 +12,18 @@ description: >
|
|
|
12
12
|
|
|
13
13
|
Judge captures at viewer resolution (1×–3×); never chase sub-visible precision.
|
|
14
14
|
|
|
15
|
-
## Default —
|
|
15
|
+
## Default — web_screenshot (pi-web ≥0.7.0, auto local detection)
|
|
16
|
+
|
|
17
|
+
`web_screenshot` auto-routes localhost/LAN/file URLs to the locally installed
|
|
18
|
+
headless Chrome and returns the PNG inline — no daemon, no manual commands:
|
|
19
|
+
|
|
20
|
+
- `web_screenshot url="http://localhost:PORT"` — done; the model sees the render.
|
|
21
|
+
- `full_page=true` captures a tall 8000px window; `wait_for` settles JS via
|
|
22
|
+
`--virtual-time-budget`; `engine="local"` forces local on a public URL.
|
|
23
|
+
- `web_pdf` works the same way (`--print-to-pdf`) for full-content archival.
|
|
24
|
+
- If Chrome is missing: `web_status` shows `localChrome.path`; set `CHROME_PATH`.
|
|
25
|
+
|
|
26
|
+
## Fallback — manual headless Chrome (pi-web <0.7.0 or if the tool errors)
|
|
16
27
|
|
|
17
28
|
Headless Chrome writes the PNG; the `read` tool shows it inline (multimodal models see it).
|
|
18
29
|
|