@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 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 # toggle / show status
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
@@ -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
- return { type: "set-mode", mode: fallback === "off" ? "strict" : fallback };
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.6",
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 — local capture (offline, no daemon)
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