start-vibing 4.3.3 → 4.4.0

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 (45) hide show
  1. package/package.json +2 -2
  2. package/template/.claude/agents/sd-audit.md +32 -0
  3. package/template/.claude/skills/e2e-audit/DESIGN.md +294 -0
  4. package/template/.claude/skills/e2e-audit/SKILL.md +660 -0
  5. package/template/.claude/skills/e2e-audit/e2e/fixtures/auth.setup.ts +70 -0
  6. package/template/.claude/skills/e2e-audit/e2e/fixtures/auth.ts +21 -0
  7. package/template/.claude/skills/e2e-audit/e2e/fixtures/base.ts +90 -0
  8. package/template/.claude/skills/e2e-audit/e2e/fixtures/storage/.gitkeep +0 -0
  9. package/template/.claude/skills/e2e-audit/e2e/fixtures/storage/admin.json +50 -0
  10. package/template/.claude/skills/e2e-audit/e2e/fixtures/storage/manager.json +50 -0
  11. package/template/.claude/skills/e2e-audit/e2e/fixtures/storage/member.json +50 -0
  12. package/template/.claude/skills/e2e-audit/e2e/fixtures/storage/owner.json +50 -0
  13. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-admin.page.ts +141 -0
  14. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-billing.page.ts +47 -0
  15. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-chat.page.ts +35 -0
  16. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-home.page.ts +134 -0
  17. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-integrations.page.ts +334 -0
  18. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-knowledge.page.ts +30 -0
  19. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-ontology.page.ts +71 -0
  20. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-profile.page.ts +38 -0
  21. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-teams.page.ts +123 -0
  22. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-transcripts.page.ts +109 -0
  23. package/template/.claude/skills/e2e-audit/e2e/specs/auth/login.spec.ts +59 -0
  24. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-admin.spec.ts +233 -0
  25. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-billing.spec.ts +44 -0
  26. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-chat.spec.ts +50 -0
  27. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-home.spec.ts +243 -0
  28. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-integrations.spec.ts +472 -0
  29. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-knowledge.spec.ts +57 -0
  30. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-ontology.spec.ts +72 -0
  31. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-profile.spec.ts +48 -0
  32. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-teams.spec.ts +247 -0
  33. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-transcripts.spec.ts +122 -0
  34. package/template/.claude/skills/e2e-audit/e2e/specs/security/headers.spec.ts +39 -0
  35. package/template/.claude/skills/e2e-audit/e2e/specs/security/rbac.spec.ts +92 -0
  36. package/template/.claude/skills/e2e-audit/e2e/specs/security/xss.spec.ts +74 -0
  37. package/template/.claude/skills/e2e-audit/e2e/utils/console-collector.ts +89 -0
  38. package/template/.claude/skills/e2e-audit/e2e/utils/security-helpers.ts +114 -0
  39. package/template/.claude/skills/e2e-audit/e2e/utils/test-data.ts +64 -0
  40. package/template/.claude/skills/e2e-audit/runbook.md +115 -0
  41. package/template/.claude/skills/super-design/SKILL.md +42 -4
  42. package/template/.claude/skills/super-design/references/audit-methodology.md +63 -7
  43. package/template/.claude/skills/super-design/scripts/discover-surfaces.sh +197 -0
  44. package/template/.claude/skills/super-design/scripts/extract-project-rules.sh +240 -0
  45. package/template/.claude/skills/super-design/scripts/verify-audit.sh +34 -1
@@ -0,0 +1,114 @@
1
+ import { type Page, expect } from '@playwright/test'
2
+
3
+ export const XSS_PAYLOADS = [
4
+ '<script>alert("xss")</script>',
5
+ '<img src=x onerror=alert("xss")>',
6
+ '"><script>alert("xss")</script>',
7
+ "javascript:alert('xss')",
8
+ '<svg onload=alert("xss")>',
9
+ "'; DROP TABLE users; --",
10
+ '{{constructor.constructor("return this")()}}',
11
+ '${7*7}',
12
+ ]
13
+
14
+ export const REQUIRED_SECURITY_HEADERS = [
15
+ 'x-frame-options',
16
+ 'x-content-type-options',
17
+ 'referrer-policy',
18
+ ]
19
+
20
+ /**
21
+ * Test XSS payloads against a text input.
22
+ * Fills each payload and verifies no dialog (alert/confirm/prompt) fires.
23
+ */
24
+ export async function testXssOnInput(
25
+ page: Page,
26
+ inputLocator: ReturnType<Page['getByRole']>,
27
+ ) {
28
+ const dialogFired: string[] = []
29
+ const handler = (dialog: { message: () => string; dismiss: () => Promise<void> }) => {
30
+ dialogFired.push(dialog.message())
31
+ void dialog.dismiss()
32
+ }
33
+ page.on('dialog', handler)
34
+
35
+ for (const payload of XSS_PAYLOADS) {
36
+ await inputLocator.clear()
37
+ await inputLocator.fill(payload)
38
+ // Trigger potential execution (blur, submit)
39
+ await inputLocator.press('Tab')
40
+ }
41
+
42
+ page.off('dialog', handler)
43
+
44
+ if (dialogFired.length > 0) {
45
+ throw new Error(
46
+ `XSS detected! Dialog(s) fired with: ${dialogFired.join(', ')}`,
47
+ )
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Verify required security headers are present on a response.
53
+ */
54
+ export async function assertSecurityHeaders(page: Page, url: string) {
55
+ const response = await page.goto(url)
56
+ if (!response) throw new Error(`No response for ${url}`)
57
+
58
+ const headers = response.headers()
59
+
60
+ for (const header of REQUIRED_SECURITY_HEADERS) {
61
+ expect(
62
+ headers[header],
63
+ `Missing security header: ${header}`,
64
+ ).toBeDefined()
65
+ }
66
+
67
+ // X-Content-Type-Options should be nosniff
68
+ if (headers['x-content-type-options']) {
69
+ expect(headers['x-content-type-options']).toBe('nosniff')
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Verify that error responses don't contain stack traces.
75
+ */
76
+ export async function assertNoStackTraceInResponse(page: Page) {
77
+ const content = await page.content()
78
+ const stackTraceIndicators = [
79
+ 'at Object.',
80
+ 'at Module.',
81
+ 'at Function.',
82
+ 'node_modules/',
83
+ '.js:',
84
+ 'webpack-internal',
85
+ '__next',
86
+ ]
87
+
88
+ for (const indicator of stackTraceIndicators) {
89
+ expect(content).not.toContain(indicator)
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Test RBAC by navigating to a URL and checking if access is denied.
95
+ */
96
+ export async function assertAccessDenied(
97
+ page: Page,
98
+ url: string,
99
+ expectedRedirect?: string,
100
+ ) {
101
+ await page.goto(url)
102
+ const currentUrl = page.url()
103
+
104
+ if (expectedRedirect) {
105
+ expect(currentUrl).toContain(expectedRedirect)
106
+ } else {
107
+ // Should redirect to /unauthorized or /auth
108
+ const denied =
109
+ currentUrl.includes('/unauthorized') ||
110
+ currentUrl.includes('/auth') ||
111
+ currentUrl.includes('/login')
112
+ expect(denied).toBe(true)
113
+ }
114
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Shared test data constants for E2E tests.
3
+ * Keep this minimal — only data used across multiple test files.
4
+ */
5
+
6
+ export const ROUTES = {
7
+ // Dashboard (authenticated)
8
+ home: '/dashboard/home',
9
+ knowledge: '/dashboard/knowledge',
10
+ knowledgeDetail: (id: string) => `/dashboard/knowledge/${id}`,
11
+ chat: '/dashboard/chat',
12
+ integrations: '/dashboard/integrations',
13
+ integrationDetail: (id: string) => `/dashboard/integrations/${id}`,
14
+ integrationsNew: '/dashboard/integrations/new',
15
+ integrationsSuccess: '/dashboard/integrations/success',
16
+ teams: '/dashboard/teams',
17
+ profile: '/dashboard/profile',
18
+ admin: '/dashboard/admin',
19
+ billing: '/dashboard/billing',
20
+ ontology: '/dashboard/ontology',
21
+ transcripts: '/dashboard/transcripts',
22
+ transcriptDetail: (id: string) => `/dashboard/transcripts/${id}`,
23
+ transcriptDocument: (id: string) => `/dashboard/transcripts/documents/${id}`,
24
+
25
+ // Auth
26
+ root: '/',
27
+ authDesktop: '/auth/desktop',
28
+ authError: '/auth/error',
29
+ logout: '/auth/logout',
30
+ popupCallback: '/auth/popup-callback',
31
+ integrationCallbackSuccess: '/auth/integration/callback/success',
32
+ integrationCallbackError: '/auth/integration/callback/error',
33
+
34
+ // Other
35
+ unauthorized: '/unauthorized',
36
+ freeTrial: '/freetrial',
37
+ } as const
38
+
39
+ export const RBAC_ROUTES = {
40
+ /** Routes accessible only by ADMIN+ */
41
+ admin: [ROUTES.admin],
42
+ /** Routes accessible only by OWNER */
43
+ owner: [ROUTES.billing],
44
+ /** Routes accessible only by MANAGER+ */
45
+ manager: [ROUTES.integrationsNew],
46
+ /** Routes accessible by any authenticated user */
47
+ member: [
48
+ ROUTES.home,
49
+ ROUTES.knowledge,
50
+ ROUTES.chat,
51
+ ROUTES.integrations,
52
+ ROUTES.teams,
53
+ ROUTES.profile,
54
+ ROUTES.ontology,
55
+ ROUTES.transcripts,
56
+ ],
57
+ } as const
58
+
59
+ export const TIMEOUTS = {
60
+ navigation: 30_000,
61
+ action: 10_000,
62
+ toast: 5_000,
63
+ animation: 500,
64
+ } as const
@@ -0,0 +1,115 @@
1
+ # E2E Audit Runbook
2
+
3
+ ## Setup
4
+
5
+ ### 1. Install Playwright
6
+
7
+ ```bash
8
+ bun add -D @playwright/test
9
+ bunx playwright install chromium
10
+ ```
11
+
12
+ ### 2. Start the dev server
13
+
14
+ ```bash
15
+ bun run dev
16
+ ```
17
+
18
+ ### 3. Run tests
19
+
20
+ ```bash
21
+ # All E2E tests
22
+ bun run test:e2e
23
+
24
+ # With UI mode (visual debugger)
25
+ bunx playwright test --ui
26
+
27
+ # Specific test file
28
+ bunx playwright test tests/e2e/specs/dashboard-home.spec.ts
29
+
30
+ # By tag
31
+ bunx playwright test --grep @smoke
32
+ bunx playwright test --grep @security
33
+ bunx playwright test --grep @ux
34
+ ```
35
+
36
+ ## Test Organization
37
+
38
+ ```
39
+ tests/e2e/
40
+ ├── fixtures/ # Auth + base fixtures
41
+ │ ├── auth.ts # Per-role authentication setup
42
+ │ ├── base.ts # Extended test fixture (authenticatedPage)
43
+ │ └── storage/ # Generated auth state files (gitignored)
44
+ ├── pages/ # Page Object Models (1 per page)
45
+ ├── specs/ # Test specs (1 per page + security/)
46
+ │ └── security/ # Cross-cutting security tests
47
+ └── utils/ # Shared helpers
48
+ ├── console-collector.ts # Console message interceptor
49
+ ├── security-helpers.ts # XSS, header, RBAC helpers
50
+ └── test-data.ts # Routes, timeouts, test constants
51
+ ```
52
+
53
+ ## Adding Tests for a New Page
54
+
55
+ 1. **Create Page Object** in `tests/e2e/pages/{page-name}.page.ts`
56
+ 2. **Create Test Spec** in `tests/e2e/specs/{page-name}.spec.ts`
57
+ 3. **Create Page Spec** in `docs/e2e-audit/page-specs/{page-name}.md`
58
+ 4. **Run and fix**: `bunx playwright test tests/e2e/specs/{page-name}.spec.ts`
59
+ 5. **Update master audit** in `docs/e2e-audit/reports/master-audit.md`
60
+
61
+ ## Updating Tests After App Changes
62
+
63
+ 1. Run the e2e-audit skill to re-discover elements on changed pages
64
+ 2. Compare new page spec with existing
65
+ 3. Update POM, test spec, and page spec
66
+ 4. Run tests: `bun run test:e2e`
67
+ 5. Fix failures
68
+
69
+ ## Auth Setup
70
+
71
+ Tests use `storageState` for pre-authenticated sessions. The auth fixture at
72
+ `tests/e2e/fixtures/auth.ts` needs real login flows implemented per role.
73
+
74
+ For local development with OAuth, you may need to:
75
+ 1. Use the Cloudflare tunnel (`https://dev.hakutaku.ai`)
76
+ 2. Set `BASE_URL=https://dev.hakutaku.ai` when running tests
77
+ 3. Or mock auth via direct cookie/session injection
78
+
79
+ ## CI Integration
80
+
81
+ ```yaml
82
+ # Example GitHub Actions step
83
+ - name: E2E Tests
84
+ run: |
85
+ bunx playwright install chromium
86
+ bun run test:e2e
87
+ env:
88
+ BASE_URL: http://localhost:3000
89
+ ```
90
+
91
+ ## Debugging Failed Tests
92
+
93
+ ```bash
94
+ # View trace from failed test
95
+ bunx playwright show-trace test-results/{test-name}/trace.zip
96
+
97
+ # Run with headed browser
98
+ bunx playwright test --headed
99
+
100
+ # Run with slow motion
101
+ bunx playwright test --headed --slow-mo 500
102
+
103
+ # Debug specific test
104
+ bunx playwright test --debug tests/e2e/specs/dashboard-home.spec.ts
105
+ ```
106
+
107
+ ## Tags Reference
108
+
109
+ | Tag | Purpose | When to run |
110
+ |-----|---------|-------------|
111
+ | `@smoke` | Critical paths | Every PR |
112
+ | `@regression` | Full suite | Main merges |
113
+ | `@security` | Security tests | Weekly + before release |
114
+ | `@a11y` | Accessibility | Weekly |
115
+ | `@ux` | UX/UI validation | After UI changes |
@@ -8,7 +8,7 @@ description: >
8
8
  UX audit (WCAG 2.2 AA, Nielsen heuristics, Baymard, CWV), and synthesized
9
9
  overview. Re-audits only what changed since last run. On explicit user request,
10
10
  applies surgical fixes with full rollback.
11
- version: 0.6.3
11
+ version: 0.7.0
12
12
  ---
13
13
 
14
14
  # super-design
@@ -22,17 +22,36 @@ Four-phase pipeline with 6 specialist agents:
22
22
  nav, cards, modals, forms, tokens — per competitor × mobile+desktop),
23
23
  produces market-analysis.md + component-comparison.md.
24
24
  2. **UI/UX audit** (sd-audit) — drives browser via Playwright MCP directly.
25
- Five layers:
25
+ Six layers:
26
26
  - Route discovery + static snap (Nielsen + WCAG 2.2 AA + Baymard + CWV)
27
+ - **Step 1.5 source-first discovery** (0.7.0+) —
28
+ `discover-surfaces.sh` reads the repo FIRST and emits an authoritative
29
+ inventory of modals, forms, triggers, internal nav, and Next.js
30
+ layout/error/loading/not-found/parallel/intercepting routes BEFORE
31
+ Playwright runs. `extract-project-rules.sh` parses FORBIDDEN tables
32
+ from CLAUDE.md / AGENTS.md / .cursorrules into audit-applicable
33
+ rules. Runtime cross-checks surface these as `modal-coverage-gap`,
34
+ `form-coverage-gap`, and `project-forbidden-<slug>` findings.
27
35
  - **Step 2.5 component/modal/flow discovery** (Phase A inventory, B modal
28
36
  enumeration, C flow exercising, D state matrix, E form coverage) — this
29
37
  is where modal contents, empty/loading/error states, and flow errors
30
- get real evidence instead of "checklist hypothetical".
38
+ get real evidence instead of "checklist hypothetical". Phase B now
39
+ cross-references `surfaces.json` and files a `modal-coverage-gap`
40
+ finding for anything declared in source but never opened.
31
41
  - **Step 3g design-intelligence scoring** (17-category rubric → DIS 0–100)
32
42
  catches implicit best practices checklists miss (cards-in-flex-col,
33
- low density, weak CTA hierarchy, vibecode smell).
43
+ low density, weak CTA hierarchy, vibecode smell). Emits MANDATORY
44
+ `design-intelligence-craft-summary` finding per page × viewport so
45
+ overview.md has one holistic verdict row ("admin mobile is 38/100
46
+ WEAK — holistic redesign scope") per combination, not just discrete
47
+ per-category findings.
34
48
  - **Step 3h mobile-native audit** (21-item Duolingo/Linear/Arc/Cash-App
35
49
  checklist) — replaces "responsive-web-on-a-phone" thinking.
50
+ - **Step 3i project-rule enforcement** (0.7.0+) — consumes
51
+ `project-rules.json` and fires primary findings keyed to the
52
+ project's own FORBIDDEN wording (e.g. `project-forbidden-use-cards-on-mobile`)
53
+ when the rule is violated at runtime. Not a tag, not a severity
54
+ bump — the project owner's rule IS the rule source.
36
55
  - C16 ≤ 4 → **DSC-choice** proposal: sd-synthesis runs
37
56
  `scripts/score-typeui.mjs --from-audit <dir>` to derive a 7-axis site
38
57
  fingerprint (density/contrast/geometry/color/typography/motion/audience)
@@ -189,6 +208,25 @@ Windows git-bash + Linux.
189
208
  `*.fixture.json`, `fixtures/<name>.json`, or `$SUPER_DESIGN_FIXTURES`
190
209
  env JSON; falls back to `@fixture-default` with a warning. Consumers
191
210
  (hash-pages, sd-audit) MUST strip the suffix before navigating.
211
+ - `discover-surfaces.sh` (0.7.0+) — source-first static scan for
212
+ modals (`<Dialog|Sheet|Drawer|Modal|Popover|AlertDialog|DropdownMenu|CommandDialog|...>`),
213
+ forms (`<form>` / `useForm(` / `<Form>`), triggers (`<*Trigger>`),
214
+ internal navigation (`<Link href>` / `router.push`), and Next.js
215
+ `layout.tsx` / `error.tsx` / `loading.tsx` / `not-found.tsx` / parallel
216
+ routes (`@foo/`) / intercepting routes (`(.)foo/`). Emits
217
+ `$SESSION_DIR/surfaces.json`. sd-audit Step 2.5 Phase B cross-checks
218
+ runtime observations against this inventory and files
219
+ `modal-coverage-gap` / `form-coverage-gap` findings for declared
220
+ components never exercised.
221
+ - `extract-project-rules.sh` (0.7.0+) — parses FORBIDDEN tables from
222
+ `CLAUDE.md` / `AGENTS.md` / `GEMINI.md` / `.claude/CLAUDE.md` /
223
+ `.cursorrules` into an authoritative rule source. Classifies each
224
+ rule as audit-applicable (mobile / design / ux / perf / a11y) or
225
+ code-level (skip — belongs to typecheck/lint). Emits
226
+ `$SESSION_DIR/project-rules.json`. sd-audit Step 3i fires primary
227
+ findings keyed to the project's own wording (e.g.
228
+ `project-forbidden-use-cards-on-mobile`) — the project owner's rule
229
+ IS the rule source, not a tag or severity bump.
192
230
  - `build-import-graph.sh` — builds `.super-design/import-graph.json`
193
231
  (`{nodes, edges, hash, backend}`) and persists `import_graph_sha` to
194
232
  state. Prefers `npx madge --json <roots>`; falls back to a regex
@@ -311,13 +311,69 @@ source section cited.
311
311
 
312
312
  > Source: §3.4.
313
313
 
314
- **`baymard-search-*` — Search (12 rules, §3.6):** placeholders for 12 rules covering autocomplete presence, scope suggestions in autocomplete, search-within-current-category, autodirect on category match, query-term pluralization tolerance, typo tolerance, "no results" with recovery options, recent-searches memory, sort-vs-filter separation, faceted-search state in URL, submit-without-suggestion-selection, voice search on mobile. Rules `baymard-search-01` … `baymard-search-12`. Source: §3.6 bullets + https://baymard.com/ecommerce-design-examples/34-autocomplete-suggestions. Enumerate the exact wording when next Baymard PDF is purchased.
315
-
316
- **`baymard-filter-*` — Filter (10 rules, §3.6):** placeholders `baymard-filter-01` `baymard-filter-10` covering: promote top filters above the product grid; truncate long value lists >10 with styled "More" link; category-specific filters (megapixels, temperature rating); filters for every attribute displayed in list items; expand/collapse icons right-aligned; applied-filter pills visible and individually removable; "clear all" affordance; range sliders with keyboard + numeric input; multi-select affordance obvious; result count live-updated via `aria-live`. Source: §3.6 bullets + https://baymard.com/blog/promoting-product-filters + https://baymard.com/blog/have-filters-for-list-item-info.
317
-
318
- **`baymard-bread-*` — Breadcrumbs (6 rules, §3.7):** placeholders `baymard-bread-01` `baymard-bread-06` covering: present on all non-home pages; implement BOTH hierarchy-based AND history-based (68% of top 50 sub-par, 45% only hierarchy, 23% none); present on mobile (65% mobile fail); no "hidden in more" collapse on desktop without reveal; last crumb non-clickable; structured-data markup (`BreadcrumbList`). Source: §3.7 + https://baymard.com/blog/ecommerce-breadcrumbs.
319
-
320
- **`baymard-pdp-*` — PDP / Product Detail (18 rules, §3.8):** placeholders `baymard-pdp-01` `baymard-pdp-18` covering: single dominant Add-to-Cart (no 3–6 competing colorful buttons); shipping cost/ETA visible on PDP (64% of users look for it); total visible before checkout (24% abandon otherwise); accordion over horizontal tabs (67% of accordion users mis-implement; 28% use worst-performing tabs); UGC visuals present (67% lack them); minimum 3–5 images + zoom + variant-driven imagery; swatches not dropdowns for variants; out-of-stock variants visible-but-disabled (not removed); star ratings above the fold (up to +18% conversion with verified badges); stock urgency without dark patterns; delivery-date estimator; returns policy on PDP; size guide inline (not in a separate page); Q&A / reviews with filtering; cross-sell without shoving below CTA; "notify me" flow for OOS; price history for discount honesty; country/currency switcher persisted. Source: §3.8 + https://baymard.com/research/product-page.
314
+ **`baymard-search-*` — Search (12 rules, §3.6):**
315
+ 1. `baymard-search-01` persist search query in the field after submission
316
+ 2. `baymard-search-02` autocomplete list capped at 10 items desktop, 4–8 mobile
317
+ 3. `baymard-search-03` style category-scope suggestions distinctly within autocomplete
318
+ 4. `baymard-search-04` highlight the predictive (un-typed) portion of autocomplete suggestions
319
+ 5. `baymard-search-05` copy active autocomplete suggestion into the field on keyboard navigation
320
+ 6. `baymard-search-06` support misspellings in autocomplete (spell-tolerant suggestions)
321
+ 7. `baymard-search-07` autodirect exact category-match queries to the category page
322
+ 8. `baymard-search-08` support synonym and alternate-term searches
323
+ 9. `baymard-search-09` support abbreviation and symbol searches
324
+ 10. `baymard-search-10` allow "search within" current category on mobile
325
+ 11. `baymard-search-11` support non-product queries (return policy, order status, FAQ)
326
+ 12. `baymard-search-12` provide 5 proven no-results recovery paths (spelling fix, broaden, contact, etc.)
327
+
328
+ > Source: §3.6 bullets + https://baymard.com/ecommerce-design-examples/34-autocomplete-suggestions + per-rule URLs in `/docs/research/baymard-public-rules.md` (B-S-01…B-S-12).
329
+
330
+ **`baymard-filter-*` — Filter (10 rules, §3.6):**
331
+ 1. `baymard-filter-01` provide the 5 essential filter types (category, price, rating, brand, user-relevant attribute)
332
+ 2. `baymard-filter-02` have filters for every attribute displayed in list items
333
+ 3. `baymard-filter-03` display applied filters in a visible overview area, individually removable
334
+ 4. `baymard-filter-04` use OR logic within a facet, AND logic across facets
335
+ 5. `baymard-filter-05` show product counts per filter value and update them dynamically
336
+ 6. `baymard-filter-06` collapse long facet lists to top 4–6 values with a "Show more" link
337
+ 7. `baymard-filter-07` explain industry-specific or jargon filter labels
338
+ 8. `baymard-filter-08` promote important filters above the product grid / above the fold
339
+ 9. `baymard-filter-09` provide 4 essential sort options (featured, price asc/desc, rating, newest)
340
+ 10. `baymard-filter-10` persist filter state when user returns from a product detail page
341
+
342
+ > Source: §3.6 bullets + https://baymard.com/blog/promoting-product-filters + https://baymard.com/blog/have-filters-for-list-item-info + per-rule URLs in `/docs/research/baymard-public-rules.md` (B-F-01…B-F-10).
343
+
344
+ **`baymard-bread-*` — Breadcrumbs (6 rules, §3.7):**
345
+ 1. `baymard-bread-01` provide both hierarchy-based AND history-based breadcrumbs
346
+ 2. `baymard-bread-02` include the full category hierarchy in the breadcrumb trail
347
+ 3. `baymard-bread-03` suppress homepage and current product page layers from the trail
348
+ 4. `baymard-bread-04` make breadcrumbs swipeable on mobile for long paths
349
+ 5. `baymard-bread-05` use clear tappability cues on mobile breadcrumbs (size, underline, chevron)
350
+ 6. `baymard-bread-06` expose `BreadcrumbList` structured-data markup for SEO and assistive tech
351
+
352
+ > Rules 01–05 from verified public research (B-B-01…B-B-05). Rule 06 enumerated from §3.7 narrative; supersede when Baymard Premium IDs become available.
353
+ > Source: §3.7 + https://baymard.com/blog/ecommerce-breadcrumbs + per-rule URLs in `/docs/research/baymard-public-rules.md`.
354
+
355
+ **`baymard-pdp-*` — PDP / Product Detail (18 rules, §3.8):**
356
+ 1. `baymard-pdp-01` use button-style size selectors (swatches), not dropdowns
357
+ 2. `baymard-pdp-02` provide at least one in-scale product image (size/context reference)
358
+ 3. `baymard-pdp-03` show product on a human model for worn or carried items
359
+ 4. `baymard-pdp-04` provide sufficient image resolution and zoom on desktop and mobile
360
+ 5. `baymard-pdp-05` support both pinch and double-tap zoom gestures on mobile
361
+ 6. `baymard-pdp-06` allow guest users to save / wishlist items without account creation
362
+ 7. `baymard-pdp-07` display a return-policy link or summary directly on the product page
363
+ 8. `baymard-pdp-08` show the lowest shipping-cost estimate (and ETA) on the product page
364
+ 9. `baymard-pdp-09` allow carousel navigation across reviewer-submitted images (UGC)
365
+ 10. `baymard-pdp-10` respond publicly to negative reviews to demonstrate accountability
366
+ 11. `baymard-pdp-11` display price-per-unit for multi-quantity or weight-priced products
367
+ 12. `baymard-pdp-12` synchronize product data (price, stock, images) across variation selections
368
+ 13. `baymard-pdp-13` include descriptive text or graphics on key product images (annotations)
369
+ 14. `baymard-pdp-14` single dominant Add-to-Cart CTA — not 3–6 competing colorful buttons
370
+ 15. `baymard-pdp-15` prefer accordion over horizontal tabs for PDP detail sections
371
+ 16. `baymard-pdp-16` show out-of-stock variants as visible-but-disabled (never remove from UI)
372
+ 17. `baymard-pdp-17` offer a "notify me when back in stock" flow for OOS variants
373
+ 18. `baymard-pdp-18` surface size-guide inline on the PDP — not hidden behind a separate page
374
+
375
+ > Rules 01–13 from verified public research (B-P-01…B-P-13). Rules 14–18 enumerated from §3.8 narrative; supersede when Baymard Premium IDs become available.
376
+ > Source: §3.8 + https://baymard.com/research/product-page + per-rule URLs in `/docs/research/baymard-public-rules.md`.
321
377
 
322
378
  ### 3.1 Cart abandonment
323
379
  **70.19% average abandonment** across 49 studies 2006–2023, range 55–84.27% (https://baymard.com/lists/cart-abandonment-rate). By device: mobile 77.06%, tablet 66.39%, desktop 70.01%. **Reasons** (excluding 43% "just browsing"): extra costs 48%; forced account creation 24%; slow delivery 19%; distrust with CC 18–19%; too long/complicated 17–18%; couldn't see total up front 16%; errors/crashes 13%; returns policy 12%; declined CC 9%; limited payment methods 7%.
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env bash
2
+ # discover-surfaces.sh — source-first discovery of modals, forms, triggers,
3
+ # internal navigation, and Next.js layout/error/loading/parallel surfaces.
4
+ #
5
+ # Purpose: sd-audit cannot trust runtime-only modal discovery (click every
6
+ # trigger in Playwright). A modal hidden behind a flow step, a feature flag,
7
+ # or an auth gate will be missed. This script reads the SOURCE CODE first
8
+ # and emits an authoritative inventory; Step 2.5 Phase B cross-checks at
9
+ # runtime and emits `modal-coverage-gap` findings for anything declared in
10
+ # source but never opened during the audit.
11
+ #
12
+ # Output: JSON object on stdout with shape:
13
+ # {
14
+ # "modals": [{ "component": "CreateUserDialog", "file": "...", "used_in": [...] }],
15
+ # "forms": [{ "id": "LoginForm", "file": "...", "fields": [...] }],
16
+ # "triggers": [{ "kind": "DialogTrigger", "file": "..." }],
17
+ # "navigation": [{ "from": "src/...", "to": "/users", "kind": "Link" }],
18
+ # "layouts": ["src/app/layout.tsx", "src/app/(app)/layout.tsx"],
19
+ # "errors": ["src/app/error.tsx"],
20
+ # "loading": ["src/app/loading.tsx"],
21
+ # "not_found": ["src/app/not-found.tsx"],
22
+ # "parallel": ["src/app/@modal"],
23
+ # "intercepting": ["src/app/(.)photos"],
24
+ # "framework": "next" | "remix" | "sveltekit" | "astro" | "nuxt" | "unknown"
25
+ # }
26
+ #
27
+ # Best-effort grep-based scanner. No AST. False positives are OK —
28
+ # sd-audit treats this as an inventory, not a contract. Missed items
29
+ # are the real failure mode.
30
+ set -euo pipefail
31
+
32
+ log() { printf '[discover-surfaces] %s\n' "$*" >&2; }
33
+
34
+ detect_framework() {
35
+ if [[ -f next.config.js || -f next.config.ts || -f next.config.mjs ]]; then echo "next"
36
+ elif [[ -f remix.config.js || -d app/routes ]]; then echo "remix"
37
+ elif [[ -f svelte.config.js && -d src/routes ]]; then echo "sveltekit"
38
+ elif [[ -f astro.config.mjs || -f astro.config.ts ]]; then echo "astro"
39
+ elif [[ -f nuxt.config.ts || -f nuxt.config.js ]]; then echo "nuxt"
40
+ else echo "unknown"; fi
41
+ }
42
+
43
+ # Source roots to scan. Frameworks differ; union keeps the scanner simple.
44
+ SCAN_ROOTS=()
45
+ for d in src app src/app src/components components src/pages pages src/features features src/routes app/routes; do
46
+ [[ -d "$d" ]] && SCAN_ROOTS+=("$d")
47
+ done
48
+ if [[ ${#SCAN_ROOTS[@]} -eq 0 ]]; then
49
+ log "no known source roots found; emitting empty inventory"
50
+ jq -n '{modals:[],forms:[],triggers:[],navigation:[],layouts:[],errors:[],loading:[],not_found:[],parallel:[],intercepting:[],framework:"unknown"}'
51
+ exit 0
52
+ fi
53
+
54
+ # File extensions we care about.
55
+ EXT_GLOB='\.(tsx|jsx|ts|js|svelte|vue|astro)$'
56
+
57
+ # --- 1. MODALS --------------------------------------------------------------
58
+ # Match common modal/overlay component usages. We key on JSX opening tags so
59
+ # we catch both <Dialog> and <Dialog.Root>. The component NAME is what
60
+ # the audit logs as the `component` field.
61
+ MODAL_PATTERN='<(Dialog|AlertDialog|Sheet|Drawer|Modal|Popover|HoverCard|Tooltip|CommandDialog|DropdownMenu|ContextMenu|Menubar|NavigationMenu|Select|Combobox|DatePicker|ColorPicker)\b'
62
+
63
+ modals_json="$(
64
+ {
65
+ for root in "${SCAN_ROOTS[@]}"; do
66
+ grep -rEHn --include='*.tsx' --include='*.jsx' --include='*.ts' --include='*.js' \
67
+ --include='*.svelte' --include='*.vue' --include='*.astro' \
68
+ "$MODAL_PATTERN" "$root" 2>/dev/null || true
69
+ done
70
+ } | awk -F: '{
71
+ file=$1; line=$2;
72
+ # capture component name between < and the next non-word char
73
+ match($0, /<([A-Z][A-Za-z0-9_]*)/, m);
74
+ if (m[1] != "") printf "{\"component\":\"%s\",\"file\":\"%s\",\"line\":%s}\n", m[1], file, line;
75
+ }' | jq -s 'unique_by([.component,.file])'
76
+ )"
77
+
78
+ # --- 2. FORMS ---------------------------------------------------------------
79
+ # Match react-hook-form useForm() calls, <Form> / <form> elements, and
80
+ # zod schema imports co-located with forms.
81
+ FORM_PATTERN='(useForm\s*\(|<Form[[:space:]>]|<form[[:space:]>])'
82
+
83
+ forms_json="$(
84
+ {
85
+ for root in "${SCAN_ROOTS[@]}"; do
86
+ grep -rEHln --include='*.tsx' --include='*.jsx' --include='*.ts' --include='*.js' \
87
+ --include='*.svelte' --include='*.vue' --include='*.astro' \
88
+ "$FORM_PATTERN" "$root" 2>/dev/null || true
89
+ done
90
+ } | sort -u | awk '{ printf "{\"file\":\"%s\"}\n", $0 }' | jq -s '.'
91
+ )"
92
+
93
+ # --- 3. TRIGGERS ------------------------------------------------------------
94
+ # Explicit *Trigger components (Radix / shadcn convention) tell us the
95
+ # connection between a button and its overlay.
96
+ TRIGGER_PATTERN='<(DialogTrigger|SheetTrigger|DrawerTrigger|PopoverTrigger|DropdownMenuTrigger|AlertDialogTrigger|HoverCardTrigger|TooltipTrigger|SelectTrigger|ComboboxTrigger|ContextMenuTrigger|MenubarTrigger|NavigationMenuTrigger)\b'
97
+
98
+ triggers_json="$(
99
+ {
100
+ for root in "${SCAN_ROOTS[@]}"; do
101
+ grep -rEHn --include='*.tsx' --include='*.jsx' \
102
+ "$TRIGGER_PATTERN" "$root" 2>/dev/null || true
103
+ done
104
+ } | awk -F: '{
105
+ file=$1; line=$2;
106
+ match($0, /<([A-Z][A-Za-z0-9_]*Trigger)/, m);
107
+ if (m[1] != "") printf "{\"kind\":\"%s\",\"file\":\"%s\",\"line\":%s}\n", m[1], file, line;
108
+ }' | jq -s '.'
109
+ )"
110
+
111
+ # --- 4. NAVIGATION ----------------------------------------------------------
112
+ # Internal nav: <Link href=...>, <Link to=...>, router.push("/..."),
113
+ # redirect("/..."), navigate("/..."). We only keep destinations that start
114
+ # with "/" (internal) — external URLs are not audit targets here.
115
+ nav_json="$(
116
+ {
117
+ for root in "${SCAN_ROOTS[@]}"; do
118
+ grep -rEHno --include='*.tsx' --include='*.jsx' --include='*.ts' --include='*.js' \
119
+ "(href|to)=[\"']/[^\"']*[\"']|(router\.push|redirect|navigate)\(\s*[\"']/[^\"']*[\"']" \
120
+ "$root" 2>/dev/null || true
121
+ done
122
+ } | awk -F: '{
123
+ file=$1; line=$2; rest=""; for (i=3;i<=NF;i++) rest = rest (i==3?"":":") $i;
124
+ # extract first "/..." path literal from the match
125
+ match(rest, /[\"'"'"']\/[^\"'"'"']*[\"'"'"']/, m);
126
+ if (m[0] != "") {
127
+ dest=m[0]; gsub(/[\"'"'"']/, "", dest);
128
+ kind = (rest ~ /push|redirect|navigate/) ? "imperative" : "link";
129
+ printf "{\"from\":\"%s\",\"to\":\"%s\",\"kind\":\"%s\",\"line\":%s}\n", file, dest, kind, line;
130
+ }
131
+ }' | jq -s 'unique_by([.from,.to])'
132
+ )"
133
+
134
+ # --- 5. NEXT.JS LAYOUT / ERROR / LOADING / NOT-FOUND / PARALLEL -------------
135
+ # Empty arrays for non-Next frameworks.
136
+ FW="$(detect_framework)"
137
+ layouts_json='[]'
138
+ errors_json='[]'
139
+ loading_json='[]'
140
+ notfound_json='[]'
141
+ parallel_json='[]'
142
+ intercepting_json='[]'
143
+
144
+ if [[ "$FW" == "next" ]]; then
145
+ # layout.tsx / template.tsx nesting defines wrapper chrome (headers,
146
+ # sidebars) — sd-audit must audit these at EVERY viewport because a
147
+ # layout that misbehaves on mobile breaks every child page.
148
+ layouts_json="$(
149
+ find app src/app -type f \( -name 'layout.tsx' -o -name 'layout.ts' -o -name 'layout.jsx' -o -name 'layout.js' -o -name 'template.tsx' -o -name 'template.ts' \) 2>/dev/null \
150
+ | jq -Rn '[inputs]'
151
+ )"
152
+ errors_json="$(
153
+ find app src/app -type f \( -name 'error.tsx' -o -name 'global-error.tsx' \) 2>/dev/null \
154
+ | jq -Rn '[inputs]'
155
+ )"
156
+ loading_json="$(
157
+ find app src/app -type f -name 'loading.tsx' 2>/dev/null | jq -Rn '[inputs]'
158
+ )"
159
+ notfound_json="$(
160
+ find app src/app -type f -name 'not-found.tsx' 2>/dev/null | jq -Rn '[inputs]'
161
+ )"
162
+ # Parallel route slots: directories starting with @
163
+ parallel_json="$(
164
+ find app src/app -type d -name '@*' 2>/dev/null | jq -Rn '[inputs]'
165
+ )"
166
+ # Intercepting routes: directories starting with (.) / (..) / (...)
167
+ intercepting_json="$(
168
+ find app src/app -type d \( -name '(.)*' -o -name '(..)*' -o -name '(...)*' \) 2>/dev/null | jq -Rn '[inputs]'
169
+ )"
170
+ fi
171
+
172
+ # --- ASSEMBLE ---------------------------------------------------------------
173
+ jq -n \
174
+ --argjson modals "$modals_json" \
175
+ --argjson forms "$forms_json" \
176
+ --argjson triggers "$triggers_json" \
177
+ --argjson navigation "$nav_json" \
178
+ --argjson layouts "$layouts_json" \
179
+ --argjson errors "$errors_json" \
180
+ --argjson loading "$loading_json" \
181
+ --argjson not_found "$notfound_json" \
182
+ --argjson parallel "$parallel_json" \
183
+ --argjson intercepting "$intercepting_json" \
184
+ --arg framework "$FW" \
185
+ '{
186
+ framework: $framework,
187
+ modals: $modals,
188
+ forms: $forms,
189
+ triggers: $triggers,
190
+ navigation: $navigation,
191
+ layouts: $layouts,
192
+ errors: $errors,
193
+ loading: $loading,
194
+ not_found: $not_found,
195
+ parallel: $parallel,
196
+ intercepting: $intercepting
197
+ }'