@stacknyu/pi-glitch-lab 0.0.0-stage → 0.5.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE +21 -0
  3. package/README.md +87 -2
  4. package/examples/glitchy-app/README.md +14 -0
  5. package/examples/glitchy-app/THIRD_PARTY_NOTICES.md +27 -0
  6. package/examples/glitchy-app/app.jsx +62 -0
  7. package/examples/glitchy-app/public/api/credits.json +1 -0
  8. package/examples/glitchy-app/public/api/me.json +1 -0
  9. package/examples/glitchy-app/public/api/messages.json +1 -0
  10. package/examples/glitchy-app/public/app.js +14749 -0
  11. package/examples/glitchy-app/public/index.html +2 -0
  12. package/examples/glitchy-app/serve.mjs +12 -0
  13. package/extensions/index.ts +157 -0
  14. package/package.json +53 -3
  15. package/scripts/affordance_scan.mjs +235 -0
  16. package/scripts/doctor.mjs +14 -0
  17. package/scripts/glitch/analyze.mjs +274 -0
  18. package/scripts/glitch_lab.mjs +519 -0
  19. package/scripts/interactions.mjs +172 -0
  20. package/scripts/journey_runner.mjs +260 -0
  21. package/scripts/lib.mjs +260 -0
  22. package/scripts/report.mjs +144 -0
  23. package/scripts/sensors/dom_observer.mjs +96 -0
  24. package/scripts/sensors/react_sensor.mjs +167 -0
  25. package/skills/ai-agentic-usability-testing/SKILL.md +68 -0
  26. package/skills/ai-agentic-usability-testing/assets/default-personas.md +96 -0
  27. package/skills/ai-agentic-usability-testing/assets/findings.example.json +56 -0
  28. package/skills/ai-agentic-usability-testing/assets/matrix.default.json +746 -0
  29. package/skills/ai-agentic-usability-testing/assets/matrix.partners.example.json +73 -0
  30. package/skills/ai-agentic-usability-testing/assets/matrix.web.example.json +32 -0
  31. package/skills/ai-agentic-usability-testing/assets/personas-template.md +45 -0
  32. package/skills/ai-agentic-usability-testing/references/latency-reliability.md +41 -0
  33. package/skills/ai-agentic-usability-testing/references/regression-pipeline.md +41 -0
  34. package/skills/ai-agentic-usability-testing/references/tenant-persona-matrix.md +61 -0
  35. package/skills/ai-agentic-usability-testing/references/visual-affordance-guide.md +97 -0
  36. package/skills/ai-agentic-usability-testing/references/visual-glitches.md +83 -0
  37. package/skills/glitch-hunt/SKILL.md +59 -0
@@ -0,0 +1,73 @@
1
+ {
2
+ "baseUrl": "https://staging.example-platform.com",
3
+ "unsafeWords": ["delete", "remove", "pay", "purchase", "buy", "subscribe", "send email", "invite", "share publicly", "logout", "log out", "sign out", "deactivate"],
4
+ "mask": ["[data-role=assistant-message]", "time", ".timestamp", ".avatar"],
5
+
6
+ "partners": {
7
+ "brightpath": { "name": "BrightPath Consultants", "vertical": "education", "url": "https://brightpath.example-platform.com" },
8
+ "globalmove": { "name": "GlobalMove Migration", "vertical": "migration", "url": "https://globalmove.example-platform.com" },
9
+ "ledgerwise": { "name": "LedgerWise Tax", "vertical": "finance", "url": "https://ledgerwise.example-platform.com" },
10
+ "meridian": { "name": "Meridian Health", "vertical": "health", "url": "https://meridian.example-platform.com" }
11
+ },
12
+
13
+ "personas": {
14
+ "teen-applicant": { "name": "Aarav, 15", "vertical": "education", "viewport": "mobile", "network": "fast3g" },
15
+ "migrating-student": { "name": "Lina, 25", "vertical": "migration", "viewport": "mobile", "network": "slow3g", "locale": "en-GB" },
16
+ "finance-analyst": { "name": "Rohan, 34", "vertical": "finance", "viewport": "desktop", "network": "fast" },
17
+ "patient": { "name": "Meera, 62", "vertical": "health", "viewport": "tablet", "network": "fast3g" }
18
+ },
19
+
20
+ "cells": [
21
+ {
22
+ "persona": "teen-applicant", "partner": "brightpath",
23
+ "login": { "url": "/login", "fields": { "input[name=email]": "teen.test@brightpath.test", "input[name=password]": "env:UX_PW_TEEN" }, "submit": "button[type=submit]", "waitFor": "/dashboard" },
24
+ "screens": ["/dashboard", "/chat", "/notes"],
25
+ "journeys": ["ask-and-save-note", "no-leaks-education"]
26
+ },
27
+ {
28
+ "persona": "patient", "partner": "meridian",
29
+ "login": { "url": "/login", "fields": { "input[name=email]": "patient.test@meridian.test", "input[name=password]": "env:UX_PW_PATIENT" }, "submit": "button[type=submit]", "waitFor": "/dashboard" },
30
+ "screens": ["/dashboard", "/chat"],
31
+ "journeys": ["start-health-chat"]
32
+ }
33
+ ],
34
+
35
+ "journeys": {
36
+ "ask-and-save-note": {
37
+ "title": "Ask about scholarships and save the answer as a note",
38
+ "steps": [
39
+ { "goto": "/dashboard" },
40
+ { "expectText": "{{partner.name}}" },
41
+ { "click": "Chat", "note": "persona would look for chat in bottom nav" },
42
+ { "fill": { "placeholder": "Ask", "value": "Which scholarships can I apply for in Canada?" } },
43
+ { "click": { "role": "button", "name": "Send" } },
44
+ { "measureResponse": { "selector": "[data-role=assistant-message]", "timeout": 60000 } },
45
+ { "click": "Save to notes" },
46
+ { "waitForText": "Saved" },
47
+ { "click": "Notes" },
48
+ { "expectText": "scholarship" }
49
+ ]
50
+ },
51
+ "no-leaks-education": {
52
+ "title": "Education persona sees no other partners or verticals",
53
+ "steps": [
54
+ { "goto": "/dashboard" },
55
+ { "expectNoText": ["GlobalMove", "LedgerWise", "Meridian", "Tax agent", "Health assistant", "undefined", "NaN", "[object Object]"] },
56
+ { "goto": "/chat" },
57
+ { "expectNoText": ["Tax agent", "Health assistant", "LedgerWise", "Meridian"] }
58
+ ]
59
+ },
60
+ "start-health-chat": {
61
+ "title": "Start a conversation about a health concern",
62
+ "steps": [
63
+ { "goto": "/dashboard" },
64
+ { "expectText": "{{partner.name}}" },
65
+ { "expectNoText": ["Tax agent", "Visa agent", "BrightPath", "LedgerWise"] },
66
+ { "click": "Ask a question" },
67
+ { "fill": { "placeholder": "Ask", "value": "I've had a mild headache for three days. What should I do?" } },
68
+ { "press": "Enter" },
69
+ { "measureResponse": { "selector": "[data-role=assistant-message]", "timeout": 60000 } }
70
+ ]
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "_readme": "Replace the target, selectors and expected outcomes with approved test-environment values. Run observation first. Browser actions need interact=true; login/fill/save need allowWrites=true as well. No server-data rollback is provided.",
3
+ "platform": { "name": "Test application", "url": "http://localhost:3000" },
4
+ "mask": ["input[type=password]", "[data-private]"],
5
+ "personas": { "tester": { "name": "Test user", "viewport": "desktop" } },
6
+ "cells": [{ "persona": "tester", "screens": ["/dashboard"], "journeys": ["navigation", "profile-persistence"] }],
7
+ "journeys": {
8
+ "navigation": {
9
+ "title": "Open a menu and verify navigation",
10
+ "steps": [
11
+ { "goto": "/dashboard" },
12
+ { "expectVisible": { "role": "heading", "name": "Dashboard" } },
13
+ { "click": { "role": "button", "name": "Account" } },
14
+ { "expectVisible": { "role": "menuitem", "name": "Profile" } },
15
+ { "click": { "role": "menuitem", "name": "Profile" } },
16
+ { "expectURL": "/profile" },
17
+ { "expectVisible": { "label": "Display name" } }
18
+ ]
19
+ },
20
+ "profile-persistence": {
21
+ "title": "Save an approved test profile and verify persistence",
22
+ "steps": [
23
+ { "goto": "/profile" },
24
+ { "fill": { "label": "Display name", "value": "QA test user" } },
25
+ { "click": { "role": "button", "name": "Save" } },
26
+ { "expectText": "Profile saved" },
27
+ { "reload": true },
28
+ { "expectValue": { "label": "Display name", "value": "QA test user" } }
29
+ ]
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,45 @@
1
+ # Personas
2
+
3
+ One block per persona. Keep one per vertical plus any high-risk variants. Concrete goals and real user quotes make the audit sharper.
4
+
5
+ ---
6
+
7
+ ## persona-id: teen-applicant
8
+
9
+ - **Name, age & situation:** Aarav, 15, applying for undergraduate admission abroad
10
+ - **Vertical / persona tag in the platform:** education
11
+ - **Typical partner(s):** e.g. BrightPath Consultants
12
+ - **Device & viewport:** mobile | tablet | desktop
13
+ - **Network:** fast | fast3g | slow3g
14
+ - **Language & reading level:** e.g. English, second language, reads quickly and skims
15
+ - **Tech comfort:** low | medium | high
16
+ - **Top tasks (in their words, priority order):**
17
+ 1. "Find which universities I can get into with my grades"
18
+ 2. "Ask about scholarships and save the answer"
19
+ 3. "See what documents I still need"
20
+ - **What success looks like to them:**
21
+ - **Known frustrations / real quotes:**
22
+ - **Words they use / words they don't know:** says "college", doesn't know "LOR"
23
+ - **Accessibility needs:**
24
+ - **Sensitive data involved:** e.g. grades, passport; health or tax for other personas
25
+
26
+ ---
27
+
28
+ ## persona-id:
29
+
30
+ - **Name, age & situation:**
31
+ - **Vertical / persona tag in the platform:**
32
+ - **Typical partner(s):**
33
+ - **Device & viewport:**
34
+ - **Network:**
35
+ - **Language & reading level:**
36
+ - **Tech comfort:**
37
+ - **Top tasks (in their words, priority order):**
38
+ 1.
39
+ 2.
40
+ 3.
41
+ - **What success looks like to them:**
42
+ - **Known frustrations / real quotes:**
43
+ - **Words they use / words they don't know:**
44
+ - **Accessibility needs:**
45
+ - **Sensitive data involved:**
@@ -0,0 +1,41 @@
1
+ # Perceived Speed & Reliability
2
+
3
+ Users don't experience backend latency; they experience **waiting without knowing why**. Measure and judge what's visible.
4
+
5
+ ## Thresholds (what the user feels)
6
+
7
+ | Moment | Good | Needs work | Finding |
8
+ |---|---|---|---|
9
+ | Visible response to a click/tap (pressed state, spinner, navigation start) | < 100 ms | 100–300 ms | > 300 ms with nothing visible: S2 on primary actions |
10
+ | Page or view usable after navigation | < 1 s | 1–3 s | > 3 s without skeleton/progress: S2 |
11
+ | AI: first sign of work (typing/thinking indicator) | < 300 ms | 300 ms–1 s | > 1 s blank after send: S2 |
12
+ | AI: first streamed words | < 2 s | 2–5 s | > 5 s with only a spinner: S2; no indicator at all: S1 |
13
+ | Long task (research, report, bulk processing) | progress + background | spinner only | blocking modal / no progress > 10 s: S2 |
14
+
15
+ Test on the persona's network. The teen on mobile data and the migrant on hostel Wi-Fi feel `slow3g`/`fast3g`, not office fiber.
16
+
17
+ ## How to measure with the scripts
18
+
19
+ - `ux_affordance_scan` with explicitly authorized `probeClicks=true, interact=true`: time from click to first observable change. No-observable-response is inconclusive, not proof of a dead click.
20
+ - `ux_journeys` step `ms`: how long each step took to complete (including waiting for text/response).
21
+ - `measureResponse` step: `firstOutputMs` (time to first visible output in the reply container) and `completeMs` (when it stopped changing). Point the selector at the latest assistant message container.
22
+ - Record the same journey 3 times and report the median and worst. AI latency varies a lot, and the worst case is what users remember.
23
+
24
+ ## Reliability signals to look for
25
+
26
+ - **Silent failure**: an asserted expected outcome did not occur and no error was shown. Distinguish this from an explorer not observing a valid clipboard/download/hover action.
27
+ - **Partial failure**: one widget fails and blanks the whole dashboard, or chat fails and loses the typed message.
28
+ - **Stuck states**: spinner that never ends; "Generating…" after the stream died. Check the screen 30–60 s later.
29
+ - **Console errors and failed requests** captured by the scripts: correlate with what the user saw. A 500 behind a friendly retry is fine; a 500 behind a blank panel isn't.
30
+ - **Retry behavior**: is there a visible retry? Does retry duplicate the message or the action?
31
+ - **Double actions**: fast double-tap sends two messages or creates two notes.
32
+ - **Session expiry**: after a long idle, does the next action fail silently or ask to log in again without losing work?
33
+
34
+ ## Recommendations vocabulary (design level)
35
+
36
+ - Optimistic UI: show the user's message/note immediately, reconcile later.
37
+ - Skeletons shaped like the final content, not full-screen spinners.
38
+ - In-button pending state plus disabling to prevent double actions.
39
+ - Visible AI states: sending → thinking → streaming → done / failed (with retry); stop button during generation.
40
+ - Background long tasks with a progress indicator and a notification when done.
41
+ - Error messages that say what happened and what to do, in the persona's words, and keep the user's input.
@@ -0,0 +1,41 @@
1
+ # From Audit to Regression Pipeline
2
+
3
+ "We push something and something else breaks" is a missing safety net, not a UX problem. The journeys written for the audit are the fastest way to build that net, because they already describe what users must be able to do in each persona × partner cell.
4
+
5
+ ## 1. Golden journeys
6
+
7
+ - Pick 1–3 journeys per vertical that must never break (login → reach dashboard; start a chat with the right agent and get a reply; save a note; open the main widget).
8
+ - Add at least one cross-partner leak check per vertical (`expectNoText` with other partners' names and other verticals' agents).
9
+ - Keep journeys short (5–12 steps) and use visible text or roles for targets, not CSS classes, so they survive restyling.
10
+ - Mask dynamic regions in screenshots (AI replies, timestamps, avatars, counters) using `mask` in the cell config, so visual diffs only flag real layout changes.
11
+
12
+ ## 2. Baseline and compare
13
+
14
+ ```bash
15
+ # on a known-good build
16
+ # in Pi: ux_journeys baseline=".ux/baseline" with explicit interact/write permission
17
+ # Outside Pi, from the installed package folder, on an approved disposable environment:
18
+ node <pi-glitch-lab>/scripts/journey_runner.mjs --config .ux/matrix.json --all --interact --allow-writes --baseline .ux/baseline
19
+ # on every new build
20
+ node <pi-glitch-lab>/scripts/journey_runner.mjs --config .ux/matrix.json --all --interact --allow-writes --compare .ux/baseline --out .ux/runs/$BUILD_ID
21
+ node <pi-glitch-lab>/scripts/glitch_lab.mjs nav --config .ux/matrix.json --all --interact --allow-writes --repeat 2 --out .ux/runs/$BUILD_ID/glitch
22
+ node <pi-glitch-lab>/scripts/report.mjs .ux/runs/$BUILD_ID --out .ux/runs/$BUILD_ID/report.html --embed
23
+ ```
24
+
25
+ The compare run exits with code 2 when a journey breaks, a leak appears, a step becomes much slower, or a screen changes visually. Intended design changes are reviewed and then captured into a NEW baseline directory. Existing baselines and run directories are never overwritten. Evidence may contain private data; do not commit it automatically.
26
+
27
+ ## 3. Where to run it
28
+
29
+ Order of value:
30
+ 1. **On every preview/staging deploy** (e.g. Vercel preview URL, staging after merge). Block promotion to production if it fails.
31
+ 2. **Scheduled disposable staging runs**, to catch config and data drift. Do not automatically explore production or assume test accounts make arbitrary clicks safe.
32
+ 3. **After partner onboarding or branding changes**: run that partner's cells.
33
+
34
+ Run locally or in an explicitly approved automation service; no GitHub Actions is required. Respect nonzero exit codes, protect private artifacts, and provide test-account passwords through environment variables referenced as `"env:VAR_NAME"`. Flags authorize only actions the operator has actually approved; they do not roll back writes.
35
+
36
+ ## 4. Grow it deliberately
37
+
38
+ - Every S1/S2 bug that reaches users becomes a new step or journey, so it can't come back silently.
39
+ - Keep the whole suite under ~10 minutes; split by vertical if needed so failures point at an owner.
40
+ - Visual-diff flakiness usually means an unmasked dynamic area or an animation; fix the mask rather than raising the threshold.
41
+ - This complements, not replaces, unit/integration/API tests owned by engineering. Recommend the team adds contract tests for the persona-tag → agents/widgets gating logic, since that's where cross-connected features break.
@@ -0,0 +1,61 @@
1
+ > **Phase 2.** Test direct platform users first (cells without a partner). Use this guide when the user is ready to test partner / white-label experiences.
2
+
3
+ # Persona × Partner Test Matrix
4
+
5
+ A white-label, multi-vertical platform has too many combinations to test all of them. Choose cells that expose the most risk per run.
6
+
7
+ ## 1. Dimensions
8
+
9
+ - **Vertical / persona tag**: education, migration, finance, health… Each loads different agents, dashboards, widgets, knowledge bases.
10
+ - **Partner (tenant)**: branding (logo, name, colors, fonts), enabled agents, knowledge base content, custom domain/URL, possibly custom copy.
11
+ - **Device & network**: mobile/desktop; fast/slow.
12
+ - **User state**: brand-new (empty data), active (lots of data), returning after a change.
13
+ - **Role**: end user vs partner admin (if partners configure things in-app).
14
+
15
+ ## 2. Picking cells
16
+
17
+ 1. **One cell per vertical, on its most typical partner.** This is the core usability study.
18
+ 2. **Stress partners** (reuse one persona):
19
+ - Loud branding: dark or neon primary color, long name (30+ characters), wide logo, custom font.
20
+ - Minimal config: few agents enabled, empty knowledge base, default branding.
21
+ - Default / platform-owned experience.
22
+ 3. **Cross-vertical partner**, if a partner serves more than one vertical: check that tags still gate correctly.
23
+ 4. **Edge states**: new user with no data; user on slow 3G mobile; non-English locale if supported.
24
+ 5. **Pairwise, not exhaustive**: make sure every persona appears at least once on mobile and once on desktop, and every partner type is hit by at least two personas. Don't multiply everything by everything.
25
+
26
+ A good first matrix is 6–10 cells. Grow it after the first report, targeting where problems clustered.
27
+
28
+ ## 3. Tenant correctness checks (per cell)
29
+
30
+ Write these as `expectText` / `expectNoText` steps and verify visually.
31
+
32
+ **Branding**
33
+ - Partner logo, name and colors on: login/signup, main nav, empty states, error pages, loading screens, emails or PDFs previewed in-app, browser tab title and favicon.
34
+ - No flash of platform or another partner's branding during load (look at the first screenshots after navigation and after login redirects).
35
+ - Long names and wide logos don't break the header on mobile.
36
+
37
+ **Brand color side effects** (the most common white-label usability bug)
38
+ - Primary buttons still contrast with their text and with the page (≥4.5:1 text, ≥3:1 for the button shape against background).
39
+ - Primary and secondary buttons still look different.
40
+ - Links are still distinguishable from body text.
41
+ - Status colors (success/warning/error) aren't confused with the brand color (e.g. a red-branded partner making every button look like "delete").
42
+
43
+ **Gating**
44
+ - Only agents enabled by this partner *and* matching this persona's tag are visible, in pickers, suggestions, search, and deep links.
45
+ - Knowledge base / content from this partner only. Search results must not surface other partners' documents.
46
+ - Dashboards and widgets match the vertical (no tax widget for a student).
47
+ - Direct URL test: open a known URL of another vertical's page or another partner's agent. Expect a friendly "not available" page, not the content, and not a crash.
48
+ - **Any leak across partners or verticals is S1**, especially with health or financial data.
49
+
50
+ **Graceful gaps**
51
+ - Partner disabled an agent the persona's journey expects: is there a clear alternative, or a dead end?
52
+ - Empty knowledge base: does the agent say it has no partner content, or does it pretend?
53
+ - Unconfigured widget: hidden cleanly, or a broken/blank box?
54
+
55
+ ## 4. Writing the forbidden list
56
+
57
+ For each cell, build `expectNoText` from:
58
+ - Names of other partners (at least two).
59
+ - Agent names from other verticals ("Tax agent", "Health assistant", "Visa agent").
60
+ - Platform brand name, if partners should be fully white-labeled.
61
+ - Obvious error markers: "undefined", "null", "NaN", "Error:", "Something went wrong", "[object Object]", raw translation keys like `dashboard.title`.
@@ -0,0 +1,97 @@
1
+ # Visual Affordance & Comprehension Guide
2
+
3
+ How to judge, from screenshots, whether users can tell what's clickable and what it does.
4
+
5
+ ## Contents
6
+ 1. What makes something look clickable
7
+ 2. The five failure types (with what to recommend)
8
+ 3. Comprehension: does the user know what the control will do?
9
+ 4. Layout & placement: will they find it?
10
+ 5. AI-platform-specific surfaces (chat, notes, research, widgets)
11
+ 6. Persona lenses
12
+ 7. First-click test protocol
13
+
14
+ ---
15
+
16
+ ## 1. What makes something look clickable
17
+
18
+ Users read clickability from a few visual cues, roughly in this order of strength:
19
+ - **Filled shape with contrasting background** and short verb label → button.
20
+ - **Outline/border with rounded corners**, padding, short label → secondary button.
21
+ - **Colored (usually blue) or underlined text** → link.
22
+ - **Chevron `›`, arrow, or "…" at the edge of a row/card** → opens something.
23
+ - **Card with shadow and hover lift** → the whole card is clickable (weak cue; ambiguous).
24
+ - **Standard icons in toolbars** (pencil, trash, share, copy, paperclip, mic, send) → action, *if* the user knows the icon.
25
+ - **Cursor change on desktop** (only after hovering, so it doesn't help discovery, and is absent on mobile).
26
+
27
+ A control is clear when it has at least one strong cue *and* nothing nearby that isn't clickable shares that cue.
28
+
29
+ ## 2. The five failure types
30
+
31
+ | Type | What the user does | Typical cause | Recommend |
32
+ |---|---|---|---|
33
+ | **Fake affordance** (red) | Taps something repeatedly; "it's broken" | Status chips/tags/badges styled like buttons; headings in brand color; decorative icons in circles; non-clickable cards with shadow | Flat, low-emphasis style for non-interactive elements; brand-filled style reserved for actions; remove hover/shadow from static cards |
34
+ | **Hidden affordance** (orange) | Never discovers the feature | Clickable plain text, whole-row clicks with no chevron, actions only on hover, swipe-only actions | Visible button/link styling; chevrons on clickable rows; always-visible actions on touch devices |
35
+ | **Mystery icon** (purple) | Guesses, or avoids it | Icon-only buttons (sparkles, wand, brain, bolt, graph) for AI-specific actions with no label | Icon + text label for anything that isn't universal; tooltip isn't enough on mobile |
36
+ | **Tiny target** (yellow) | Misses, hits neighbor | Small icons in dense toolbars, close "×" in corners | ≥44×44px on touch, ≥24×24px desktop, 8px spacing |
37
+ | **Dead click** (probe) | Taps real control, nothing visible happens | No pending state, action happens off-screen, slow request with no spinner, disabled but looks enabled | Immediate pressed state + in-button spinner; toast or inline confirmation; visibly disabled style with reason |
38
+
39
+ Also check:
40
+ - **Primary vs secondary**: one clearly dominant primary action per view. Two filled buttons side by side = user doesn't know which.
41
+ - **Disabled look-alikes**: grey text that is actually clickable, or enabled-looking buttons that do nothing until a hidden condition is met.
42
+ - **Duplicate labels** going to different places ("Start" in header vs "Start" on card).
43
+
44
+ ## 3. Comprehension: does the user know what it will do?
45
+
46
+ Ask for each control: *before pressing, can this persona predict the result?*
47
+ - Labels are **verb + object** in the user's words: "Save as note", "Ask the visa agent", "Upload tax form". Avoid "Process", "Generate", "Run", "Submit", "Go", "Continue" without context.
48
+ - Avoid platform jargon: agent, workspace, knowledge base, context, token, prompt, widget, tenant. A patient says "ask a question", a student says "my documents".
49
+ - Consequences are clear: will it send, share, publish, charge, or delete? Is it reversible?
50
+ - Where does the result go? After "Save", can the user see where it was saved?
51
+
52
+ ## 4. Layout & placement: will they find it?
53
+
54
+ - The action sits where the eye already is: next to the thing it acts on, or at the end of the reading path (bottom-right of a form, below chat input).
55
+ - Consistent placement across screens and partners. Same action, same position.
56
+ - Nothing essential below the fold on mobile without a cue to scroll.
57
+ - Navigation labels match the persona's tasks, not the product's modules ("My applications", not "Workflows").
58
+ - Partner branding doesn't displace core controls (large logos pushing nav off-screen on mobile).
59
+
60
+ ## 5. AI-platform-specific surfaces
61
+
62
+ **Chat**
63
+ - Obvious input and send; send is enabled only when there's text; Enter behavior is predictable.
64
+ - Which agent am I talking to? Name/avatar visible; switching agents is clear and says what changes.
65
+ - Response states: thinking indicator within ~300ms, streaming visible, stop button while generating, retry on failure, clear error when the agent fails. Silent failure is S1/S2.
66
+ - Message actions (copy, save to note, regenerate, feedback) labeled or at least recognizable; not hidden on hover on touch.
67
+ - Suggested prompts look tappable and read like things this persona would ask.
68
+
69
+ **Notes / documents**
70
+ - "Save to note" from chat shows where the note went and a link to open it.
71
+ - Autosave state visible ("Saved" / "Saving…"); no ambiguous "Save" button when autosave exists.
72
+
73
+ **Research**
74
+ - Long tasks show progress, can run in the background, and notify on completion. Sources look clickable and are.
75
+
76
+ **Data & widgets**
77
+ - Each widget has a title saying what it shows, units, and time range; loading and empty states say what's happening ("No tax documents yet: upload one"); a failing widget doesn't blank the dashboard.
78
+ - Charts: clickable elements (legends, bars) either work or don't look interactive.
79
+
80
+ ## 6. Persona lenses
81
+
82
+ Apply the lens of the persona you're simulating; note when a finding affects one persona more than others.
83
+
84
+ - **Teen student (≈15, mobile-first)**: skims, taps fast, expects app-like patterns (bottom nav, big buttons), low patience for text walls, may not know admissions jargon (SOP, LOR, transcript). Fake affordances and tiny targets hurt most.
85
+ - **Migrating student (≈25, second-language English, anxious about deadlines)**: reads carefully but literally, relies on clear labels and dates, needs confidence that uploads/submissions worked, may use translation. Idioms and vague labels hurt most.
86
+ - **Financial analyst (desktop, power user)**: wants density, keyboard, exports, precise numbers with units, and doesn't tolerate waiting. Hidden bulk actions, slow widgets and ambiguous totals hurt most.
87
+ - **Patient (any age, possibly unwell, low vision or low literacy)**: needs calm, plain language, large text, reassurance about privacy, clear "this isn't a doctor / get urgent help" paths. Jargon, clutter, silent failures and anything suggesting data is shared hurt most.
88
+
89
+ ## 7. First-click test protocol
90
+
91
+ For each journey step, before looking at what the script did:
92
+ 1. Look at the screenshot as the persona, with their task in mind.
93
+ 2. Write where you'd click first and why (element + reason).
94
+ 3. Compare with the correct control. If different, record a comprehension/placement finding with both locations marked.
95
+ 4. If you'd hesitate between two or more options, that's friction even if the right one wins.
96
+
97
+ First-click success correlates strongly with task success. A wrong first click on a primary task is S2.
@@ -0,0 +1,83 @@
1
+ # Visual Glitches: What They Look Like and What to Write Down
2
+
3
+ Subtle glitches ("I opened a page and something else on the screen refreshed") erode trust even when nothing is technically broken. Users describe the app as "buggy", "unstable" or "cheap". `glitch_watch.mjs` finds candidates; this guide helps you judge and describe them.
4
+
5
+ ## Contents
6
+ 1. Glitch types
7
+ 2. Judging: would a user notice?
8
+ 3. Severity
9
+ 4. How to write a glitch finding
10
+ 5. Likely causes (for the developers' benefit)
11
+ 6. Tips for running the watcher
12
+
13
+ ---
14
+
15
+ ## 1. Glitch types
16
+
17
+ | Type (script) | What the user sees | Common examples |
18
+ |---|---|---|
19
+ | **flicker** | Something blinks, flashes blank or to another state, then comes back, usually in under half a second | Sidebar/nav blinks when opening a page; widget flashes empty; avatar/logo reloads; button label switches and back; whole panel goes white for a frame |
20
+ | **remount** (DOM) | Same as flicker, but the element was fully destroyed and rebuilt, often losing scroll position, expanded sections or typed text | Chat list re-renders when a message arrives; notes list rebuilds after saving; sidebar rebuilds on each navigation |
21
+ | **text-flap** (DOM) | A number or text changes and changes back | Credits "120 → 0 → 120"; user name → "Guest" → name; "Loading…" → value → "Loading…" → value; price or index value blinking |
22
+ | **late-change** | Part of the page changes after it already looked finished; nothing the user did caused it | Widget refreshes itself 3 seconds after load; header re-renders when data arrives; a banner appears and pushes things; unrelated panel updates after clicking elsewhere |
23
+ | **layout-shift** | Content jumps position | Banner inserted at the top; image loads without reserved space; font swap; list grows above where the user is reading |
24
+
25
+ Also watch for these in screenshots and filmstrips (the script can't always label them):
26
+ - **Wrong-state flash**: logged-out header, empty state ("No notes yet") or zero values shown briefly before real data.
27
+ - **Theme/branding flash**: default colors or logo before the right ones.
28
+ - **Double loading**: skeleton → content → skeleton → content.
29
+ - **Focus/scroll jump**: page scrolls on its own, or the text cursor leaves the chat input after a reply arrives.
30
+ - **Streaming jank**: AI reply jumps, re-renders from the start, or the page auto-scrolls fighting the user who is scrolling up to read.
31
+
32
+ ## 2. Judging: would a user notice?
33
+
34
+ Open the `*_glitch_NN.png` (before / glitch / after) and the filmstrip. Then ask:
35
+ - **Size and place**: is it in the user's focus (main content, the thing they just clicked, the chat input) or peripheral? Large or central = noticeable.
36
+ - **Duration**: under ~50 ms is rarely seen; 100–500 ms is clearly visible as a blink; longer is a visible wrong state.
37
+ - **Content change**: a blink to *wrong content* (zero balance, "Guest", empty list) is worse than a blink to blank.
38
+ - **Frequency**: on every page load or navigation (high), or once in a while.
39
+ - **Persona**: a senior user at 125% zoom reading carefully will notice a paragraph jumping; a fast mobile user will notice a blinking nav.
40
+
41
+ Discard: intentional animations (spinners, typing dots, progress bars; the script marks these `probablyIntentional`), real-time data updating as designed (a live index ticking; though check that it doesn't blink or jump), and AI text streaming in the reply area (masked by default).
42
+
43
+ ## 3. Severity
44
+
45
+ - **S2**: wrong-state flash of important data (balance, name, results), a glitch on every navigation in the main layout (nav/sidebar/header), a remount that loses user input or scroll, or layout jumps that cause mis-taps.
46
+ - **S3**: visible blink/refresh in secondary areas, late self-refresh of a widget, small shifts.
47
+ - **S4**: barely perceptible, rare.
48
+ - Escalate to **S1** only if the glitch makes the user act on wrong information or lose work.
49
+
50
+ ## 4. How to write a glitch finding
51
+
52
+ Plain language first, then evidence:
53
+ > **UX-014 · Sidebar blinks every time a page opens (all personas).** When opening Notes, Research or Dashboard, the left sidebar disappears for about 120 ms and reappears. It makes the app feel unstable, and on the senior persona's zoomed laptop it's very visible. *Evidence:* glitch images at 1.2 s on /notes, /research; DOM shows the sidebar is rebuilt (remount). *Recommendation:* the sidebar should persist across page changes; only the main content area should update.
54
+
55
+ Always include: where (screen, region, element name if known), when (on load / after which action / how long after), how long it's visible, which personas and devices, screenshot. Group identical glitches across screens into one pattern finding.
56
+
57
+ ## 5. Likely causes (for the developers)
58
+
59
+ You aren't fixing code, but a one-line hint speeds up the fix:
60
+ - Element rebuilt on every navigation → layout/shell component re-created per page instead of persisting.
61
+ - Blink to empty/zero before data → data fetched on the client after first render, without keeping a placeholder or previous value.
62
+ - Widget refreshes after load → a second data fetch or a refresh timer; or a re-render when an unrelated piece of state (e.g. user profile, credits) arrives.
63
+ - Wrong-state flash (logged out / default theme) → session or theme resolved late in the browser.
64
+ - List rebuilt after save → whole list replaced instead of updated in place.
65
+ - Jumps → images, banners or fonts without reserved space.
66
+
67
+ ## 6. Tips for running glitch_hunt
68
+
69
+ - Run against a **production-like build** (staging), not a local dev server. Dev mode re-renders things that production doesn't, and can hide others.
70
+ - Keep `repeat` at 2 or more. Real glitches repeat; one-offs may be network noise.
71
+ - Use `mode=idle` on live-data pages and `mode=journey` for actions. Many glitches happen on actions or timers, not on page loads.
72
+ - If a region changes legitimately (live ticker, rotating tips), add its selector to the cell `mask` in the config.
73
+ - `paint=true` adds repaint frames (green = repainted), the clearest evidence for developers.
74
+ - Setting the environment variable `GLITCH_DEBUG=1` saves every raw frame for close inspection.
75
+
76
+ ## 7. Extra finding types from the React sensor
77
+
78
+ | Finding | Meaning | Typical cause |
79
+ |---|---|---|
80
+ | **X is thrown away and rebuilt** (looks the same before and after) | The component instance was destroyed and a new one created in the same place | Shared UI rendered inside each page instead of the layout; a component defined inside another component; a changing `key`; a conditional wrapper around the tree |
81
+ | **X is thrown away and rebuilt** (area changed) | Page content replaced | Usually expected on navigation; low priority |
82
+ | **X re-renders N×** (why: props / own state / parent re-rendered) | Wasted renders; invisible alone | New objects or functions passed as props on every render; state updated with equal values; a parent re-rendering on a timer |
83
+ | **"right after: GET /api/…"** | The response that preceded the glitch | Data fetched on the client and the UI reset while waiting |
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: glitch-hunt
3
+ description: "QA pass for subtle visual glitches in a running web app. Detect sidebar/header refreshing, chat box flicker or remounts, text flaps and layout jumps using Glitch Lab frame/React/DOM sensors. Use for flicker, flashing, jank, refreshing, blinking, jumping, unstable UI or a visual QA pass. Reports evidence and sensor limitations, not guaranteed root causes."
4
+ ---
5
+
6
+ # Glitch hunt
7
+
8
+ Glitches last 50–300 ms and happen *between* the moments an agent looks at the page. Never try to judge flicker from screenshots or accessibility snapshots, and never conclude "no glitches found" from them. Use `glitch_hunt`: it samples frames and bounded React/DOM sensor events. Treat attribution as a lead to verify, not proof of root cause.
9
+
10
+ ## Before the first run
11
+
12
+ - Run `glitch_lab_doctor`. If it fails, request download permission before `install=true`.
13
+ - Obtain explicit permission for interactions in a disposable test environment before `interact=true`. Login, typing, submission and server writes additionally need `allowWrites=true`; never infer that permission. Repeats can repeat writes. Use loads/idle modes for observation; page scripts and GET endpoints may still have side effects.
14
+ - Always use a new output directory. Failed/incomplete runs are errors, not clean results.
15
+ - Ask for the URL of a **staging or production-like build** and a test login. Dev servers (`next dev`) double-render and hot-reload, which creates false glitches and hides real ones. Production builds work but show minified component names ("Xe" instead of "Sidebar"). A staging build with readable names is ideal.
16
+ - For logged-in apps, use a matrix config with a `login` block and an authenticated-only positive assertion (see the usability skill's `assets/matrix.web.example.json` and package README) and pass `cell=`. For public pages, `url=` is enough.
17
+
18
+ ## The pass (in this order)
19
+
20
+ 1. **Navigation**: `glitch_hunt` with `mode=nav` and `url=<main app page>` (or `cell=`). It finds the sidebar/header links itself and walks page to page. If it picks the wrong links, pass `items="Chat,Notes,Research,…"` with the visible link texts. This catches sidebars, headers and shells that rebuild or blink on every navigation.
21
+ 2. **Idle watch** on pages with live data: `mode=idle` on the chat page and any dashboard. Nobody touches the page, so anything that blinks, rebuilds or re-renders repeatedly is the app refreshing itself (polling, timers, websocket updates). This is where "the chat box flickers" usually shows up.
22
+ 3. **Key interaction**: `mode=journey` with the chat journey (open chat, type, send, wait for the reply), plus any other flow the user complains about. Mask expected streaming regions explicitly; don't assume every streaming effect is filtered.
23
+ 4. **Other devices**: repeat step 1 with a mobile persona or `viewport=android`. Mobile layouts often have their own glitches.
24
+ 5. **Evidence run**: repeat whichever run found the most with `paint=true`. It adds frames where repainted areas show green, which is the most convincing evidence for developers.
25
+
26
+ The defaults are 2 runs per scenario and a 4× slower CPU. Use `repeat=3` when results look inconsistent, and `cpu=6` if nothing shows but the user can see glitches on their own machine.
27
+
28
+ ## Reading the results
29
+
30
+ The tool returns a ranked summary plus the strongest evidence images. For each HIGH or MEDIUM finding, open its evidence images before writing anything.
31
+
32
+ - **"X is thrown away and rebuilt"** with the note "looks the same before and after": a persistent piece of UI (sidebar, header, chat box) is being destroyed and recreated. This is the most common root cause of "refreshing" complaints. If the note says "its area changed", it's usually expected page content and low priority.
33
+ - **"X area blinks for N ms"**: a visible flash. Anything ≥100 ms on a main area is noticeable.
34
+ - **"X re-renders N×"** with "why it rendered": wasted work. On its own it's invisible, but it matters when it lines up with a blink or with slowness.
35
+ - **"text changes and changes back"**: wrong or empty content flashes, for example "120 → … → 120" or messages → empty → messages.
36
+ - **"right after: GET /api/…"**: the network response that triggered it. This is a big hint for developers.
37
+ - **"seen … 2/2 runs"**: it repeats. Findings seen in only 1 of N runs are probably noise unless the user reported them.
38
+ - **Framework internals** (Next.js routers, boundaries) are ranked low. Mention them only if nothing else explains a blink.
39
+
40
+ ## Writing it up
41
+
42
+ One finding per root cause, not per screen. Merge "Sidebar is rebuilt" and "Sidebar area blinks" into a single finding.
43
+
44
+ For each finding:
45
+ - **What the user sees**, in plain words, and on which pages or devices.
46
+ - **How often**: every navigation, while idle every N seconds, or only after a specific action.
47
+ - **Evidence**: the before/after image and the repaint frames (`read` the PNG paths from the summary).
48
+ - **Likely cause**, in one line, using the component name, the "why it rendered" reason and the triggering request. For example: "Sidebar is created inside each page instead of the shared layout", "ChatBox component is defined inside ChatPage, so it's a new component on every render", "messages are cleared before refetching", "credits are reset to empty on every route change".
49
+ - **Severity**: HIGH when it's in the main layout on every navigation, rebuilds something the user is typing in, or flashes wrong data.
50
+
51
+ Finish with `ux_report` (`runDir` = the output folder) for a shareable HTML report.
52
+
53
+ You may read the source code **after** a finding is established, to confirm the cause and point to the file. The detection itself stays black-box.
54
+
55
+ ## Honesty
56
+
57
+ - If `React sensor: no React detected`, only the visual and DOM sensors ran. Say so, and don't invent component names.
58
+ - If a run errored (login failed, links not found), report that rather than "no glitches".
59
+ - A clean run means no glitches were detected under these conditions, not that none exist. State the pages, devices and runs covered.