diff --git a/docs/superpowers/plans/2026-05-14-a11y-rescan-live-portal.md b/docs/superpowers/plans/2026-05-14-a11y-rescan-live-portal.md new file mode 100644 index 00000000..a57b5663 --- /dev/null +++ b/docs/superpowers/plans/2026-05-14-a11y-rescan-live-portal.md @@ -0,0 +1,552 @@ +# A11y Rescan — Live Portal (Authenticated + Guest) Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Provide accurate, current a11y coverage for the entire live Vue portal (guest + authenticated routes), close any real production findings, and document the new baseline. Continuation of Audit #3 Phase 7 + deferred-fixes sprint Task 1 — closes the `out-of-scope` clause for authenticated routes. + +**Architecture:** Three-pillar a11y verification — (1) Pa11y on guest pages (existing baseline, verify holds); (2) Pa11y on authenticated pages via per-URL `actions` login flow (new); (3) axe-core via Playwright MCP sweep on the same authenticated routes (cross-validation against Pa11y). Real findings are fixed inline; known temp (DevIndexBadge) and Vuetify-structural patterns are documented as ignored. + +**Tech Stack:** pa11y-ci 4.1 (chromium + axe rules) + Playwright MCP (browser_navigate + browser_evaluate for axe-core CDN) + Laravel 13 dev-server (`php artisan serve :8000`) + Vue Router (createWebHistory) + Sanctum SPA cookie auth + DemoSeeder credentials `admin@demo.local:password`. + +**Scope:** + +- **In scope:** All 26 SPA routes registered in `app/routes/web.php` (7 guest already in baseline + 14 authenticated + 3 error views + 2 already counted). +- **Out of scope:** Static HTML handoff prototypes (already relocated to opt-in `pa11y-handoff.config.json` in deferred-fixes sprint). Mobile viewport (1440×900 desktop baseline is sufficient for first authenticated baseline). +- **Branch:** work on `main` (current 🟢 GREEN, HEAD `ae20033`). Atomic commit per logical change. + +**Pre-flight assumptions to verify in Task 1:** + +- Dev-server up on `http://localhost:8000` (status 200 on `/login`). +- Demo tenant + `admin@demo.local` user present (DemoSeeder runs auto in local env per commit `e746b3c` Task 2.4). +- Vite production build current (Task 1 will rebuild to be safe — quirk #81 from `feedback_environment.md`: `php artisan serve` serves built assets, not Vite HMR). + +--- + +## Task 1 — Pre-flight + verify guest baseline holds + +**Files:** + +- No modifications — verification only. + +### Step 1.1 — Verify dev-server up + DemoSeeder applied + +- [ ] Run from `c:/моя/проекты/портал crm/Документация`: + +```bash +curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8000/login +``` + +Expected: `200`. If not 200 → start `cd app && php artisan serve --port=8000 &`. + +- [ ] Verify user count: + +```bash +cd "/c/моя/проекты/портал crm/Документация/app" && php artisan tinker --execute='echo \App\Models\User::count();' +``` + +Expected: `>= 1`. If `0` → `php artisan migrate:fresh --seed`. + +### Step 1.2 — Rebuild Vite (quirk #81) + +```bash +cd "/c/моя/проекты/портал crm/Документация/app" && npx vite build +``` + +Expected: build OK, ~2s, `app/public/build/assets/*.js` files updated. + +### Step 1.3 — Run Pa11y guest baseline + +```bash +cd "/c/моя/проекты/портал crm/Документация" && npm run a11y +``` + +Expected: `✔ 7/7 URLs passed`. If anything fails, treat as regression — investigate before continuing. + +### Step 1.4 — Capture & commit nothing (verification only) + +- [ ] Note timing in scratch — no file changes yet. + +--- + +## Task 2 — Discover login form selectors + +**Files:** + +- Read: `app/resources/js/views/auth/LoginView.vue` (selectors for email/password inputs + submit button) +- Read: `app/resources/js/views/auth/TwoFactorView.vue` (for 2FA flow if applicable — DemoSeeder user has `totp_enabled: false`, so 2FA can be skipped) + +### Step 2.1 — Read LoginView for `data-testid` / `id` / `name` attributes + +- [ ] Read full file: + +```text +app/resources/js/views/auth/LoginView.vue +``` + +Identify: + +- Email input selector (e.g. `input[type="email"]` or `#email` or `[data-testid="login-email"]`) +- Password input selector +- Submit button selector +- Success-redirect target (likely `/dashboard`) + +### Step 2.2 — Verify DemoSeeder credentials in code + +- [ ] Grep `app/database/seeders/DemoSeeder.php`: + +```bash +grep -n "admin@demo.local\|password" app/database/seeders/DemoSeeder.php +``` + +Expected: confirmation that email is `admin@demo.local` and `Hash::make('password')` is used. + +### Step 2.3 — Document selectors + +- [ ] Write findings in scratch (no commit yet): + +```text +Login flow: +- POST /login +- email input: +- password input: +- submit button: +- on success: redirect to /dashboard (Vue Router auth-guard pulls user via /api/auth/me) +``` + +--- + +## Task 3 — Extend Pa11y config with authenticated URLs (actions-based login) + +**Files:** + +- Modify: `pa11y.config.json` (add 14+ authenticated URLs with per-URL `actions` block for login) + +### Step 3.1 — Verify Pa11y `actions` API works locally on a single auth URL + +- [ ] Create temporary `pa11y-test.config.json`: + +```json +{ + "defaults": { + "standard": "WCAG2AA", + "timeout": 30000, + "wait": 1500, + "hideElements": ".js-skip-a11y, [data-a11y-skip], .dev-index-badge, .dev-index-num, .v-overlay-container", + "ignore": ["warning", "notice"], + "chromeLaunchConfig": { + "args": ["--no-sandbox", "--disable-dev-shm-usage"] + }, + "viewport": { "width": 1440, "height": 900 } + }, + "urls": [ + { + "url": "http://localhost:8000/dashboard", + "screenCapture": "./bin/a11y-screenshots/live-auth-dashboard.png", + "actions": [ + "navigate to http://localhost:8000/login", + "wait for element to be visible", + "set field to admin@demo.local", + "set field to password", + "click element ", + "wait for path to be /dashboard", + "navigate to http://localhost:8000/dashboard", + "wait for element to be visible" + ] + } + ] +} +``` + +Replace `` with selectors discovered in Task 2. + +### Step 3.2 — Run test config + +```bash +cd "/c/моя/проекты/портал crm/Документация" && npx pa11y-ci --config pa11y-test.config.json +``` + +Expected: `1/1 URL passed` (or violations listed). If Pa11y errors with «navigation timeout» or «selector not found» — debug login-flow selectors before proceeding. + +### Step 3.3 — If Step 3.2 succeeds, merge auth URLs into main config + +- [ ] Add 14 authenticated URL blocks (one per route) to `pa11y.config.json`. Each block shares the same `actions` login template (only target URL + screenshot filename change). + +Routes from `app/routes/web.php` (Route::view + auth-guarded — verify in Vue Router meta): + +1. `/dashboard` (always auth) +2. `/deals` (auth) +3. `/kanban` (auth) +4. `/projects` (auth) +5. `/billing` (auth) +6. `/settings` (auth) +7. `/reports` (auth) +8. `/reminders` (auth) +9. `/admin/tenants` (admin-auth) +10. `/admin/billing` (admin) +11. `/admin/incidents` (admin) +12. `/admin/system` (admin) +13. `/admin/pricing-tiers` (admin) +14. `/admin/supplier-prices` (admin) + +`/admin/impersonation` — covered by separate flow (`saas_admin` SSO not present on MVP per `routes/web.php:74`); for MVP, treat as authenticated like other admin. + +### Step 3.4 — Delete the temporary test config + +```bash +cd "/c/моя/проекты/портал crm/Документация" && rm pa11y-test.config.json +``` + +--- + +## Task 4 — Run extended Pa11y baseline + capture findings + +**Files:** + +- Output: terminal log + `bin/a11y-screenshots/live-auth-*.png` (auto-generated by Pa11y) + +### Step 4.1 — Full Pa11y run + +```bash +cd "/c/моя/проекты/портал crm/Документация" && npm run a11y 2>&1 | tee /tmp/pa11y-extended-baseline.log +``` + +Expected: at least one of two outcomes: + +- All ≥21 URLs pass → straight to documentation (Task 8) +- Some URLs fail with violations → categorize in Task 5 + +### Step 4.2 — Tally pass/fail per URL + +- [ ] From log, build a table: + +| URL | Status | Violations count | Notes | +|---|---|---|---| +| /login | pass/fail | N | | +| /dashboard | | | | +| ... | | | | + +### Step 4.3 — For each failing URL, capture violation details + +- [ ] Per-violation: rule name (color-contrast, label, region, etc.), DOM selector, expected ratio/text, recommendation. + +--- + +## Task 5 — Categorize findings (real prod vs known temp vs structural) + +**Files:** + +- Output: classification table in scratch + decision matrix for Task 7. + +### Step 5.1 — Triage each finding + +For each violation, decide: + +- **REAL_PROD** — fix in code (Task 7) +- **KNOWN_TEMP** — DevIndexBadge / DevIndex* — already in `hideElements`; if it leaks through, investigate why +- **STRUCTURAL_VUETIFY** — `.v-overlay-container` / portal — already ignored; investigate if new +- **CONTENT_DATA** — depends on DemoSeeder content (e.g. empty table state) — fix in test data OR component fallback +- **MIGRATED_KNOWN** — already known from Audit #3 Phase 7 (empty-table-header on /admin/tenants — should be CLOSED now since title='Действия' was set in deferred-fixes sprint commit `c524227`) + +### Step 5.2 — Decision matrix output + +```text +REAL_PROD → fix in Task 7 (atomic commit per finding) +KNOWN_TEMP → no fix; verify hideElements covers selector +STRUCTURAL → no fix; document in audit-baseline-pa11y.md +CONTENT_DATA → fix in seed/component fallback if production-relevant +MIGRATED_KNOWN→ verify fix from c524227 holds; if regressed, re-fix +``` + +--- + +## Task 6 — axe-core cross-validation via Playwright MCP + +**Files:** + +- No file changes — Playwright MCP session output. + +### Step 6.1 — Launch Playwright + login + +- [ ] Open Playwright MCP browser to `http://localhost:8000/login` +- [ ] Fill email `admin@demo.local`, password `password`, submit +- [ ] Wait for `/dashboard` + +### Step 6.2 — Inject axe-core CDN + run on each route + +For each of the 18 routes in scope: + +- [ ] `browser_navigate http://localhost:8000/` +- [ ] `browser_wait_for` until main content rendered +- [ ] `browser_evaluate`: + +```javascript +() => { + return new Promise((resolve) => { + if (!window.axe) { + const script = document.createElement('script'); + script.src = 'https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.10.0/axe.min.js'; + script.onload = () => window.axe.run(document).then(resolve); + document.head.appendChild(script); + } else { + window.axe.run(document).then(resolve); + } + }); +} +``` + +- [ ] Capture `violations[]` array per route. + +### Step 6.3 — Cross-compare Pa11y vs axe-core findings + +Build a table: + +| Route | Pa11y violations | axe-core violations | Agreement? | +|---|---|---|---| + +Pa11y uses axe-core rules under the hood, so they should largely agree. Discrepancies signal: + +- Pa11y standard (WCAG2AA) vs axe default (axe-core baseline incl. best-practice) — axe surfaces more. +- Timing differences (Pa11y waits, axe runs once snapshot). +- Visual diff (Pa11y full screencap renders Vuetify portals empty vs axe sees populated DOM). + +--- + +## Task 7 — Fix real production findings (one atomic commit per finding) + +**Files:** + +- TBD based on findings — likely Vue components in `app/resources/js/components/**`, `app/resources/js/views/**`, or scoped styles. + +### Step 7.1 — For each REAL_PROD finding, write TDD-style fix + +Per finding: + +- [ ] Identify the source file containing the offending element +- [ ] Make minimal change (scoped CSS color override / aria-label / label association / etc.) +- [ ] Rebuild Vite: `cd "/c/моя/проекты/портал crm/Документация/app" && npx vite build` +- [ ] Re-run pa11y on the affected URL only: + +```bash +cd "/c/моя/проекты/портал crm/Документация" && npx pa11y-ci --config pa11y.config.json --include "" +``` + +- [ ] Verify violation gone, no new ones introduced. + +### Step 7.2 — Commit per fix + +```bash +git add +git commit -m "fix(a11y): " +``` + +Atomic commits make rollback safe and history readable. Per Pravila — one logical change → one commit. + +--- + +## Task 8 — Update Pa11y baseline doc + +**Files:** + +- Modify: `docs/audit-baseline-pa11y.md` (extended baseline post-rescan) + +### Step 8.1 — Append «Authenticated rescan 2026-05-14» section + +```markdown +## Authenticated rescan — 2026-05-14 + +> Extension to first baseline (guest pages only). Added 14 authenticated routes +> via Pa11y `actions` per-URL login flow. DemoSeeder credentials. + +### URLs scanned (live Vue, authenticated) + +| # | URL | Pa11y exit | Violations | Status | +|---|---|---|---|---| +| 1 | /dashboard | 0/N errors | M | ✅/⚠️ | +| ... | + +### New fixes during rescan + +| URL | Violation | File | Fix commit | +|---|---|---|---| +| ... | +``` + +### Step 8.2 — Update «Authenticated pages — out of scope» section + +```markdown +~~## Authenticated pages — out of scope для первой baseline~~ + +(SUPERSEDED 2026-05-14) — see «Authenticated rescan» section above. +Implemented via Pa11y `actions` API per-URL login flow + DemoSeeder +credentials. +``` + +--- + +## Task 9 — Create audit findings document + +**Files:** + +- Create: `docs/superpowers/audits/2026-05-14-a11y-rescan-findings.md` + +### Step 9.1 — Write findings doc following Audit #3 format + +```markdown +# A11y Rescan — Live Portal Findings (2026-05-14) + +> Continuation of Audit #3 Phase 7 — closes the out-of-scope clause for +> authenticated routes after deferred-fixes sprint (commits 8ba9c55..ae20033). + +## Scope expansion + +- Pre-rescan baseline (after sprint `ae20033`): 7 guest pages, 0 violations +- Rescan adds: 14 authenticated routes (Pa11y `actions` API) + axe-core + cross-validation via Playwright MCP + +## Findings + +| ID | Severity | Rule | Route | DOM | Status | +|---|---|---|---|---|---| +| ... | + +## Per-route table + +[Reuse table from Task 8 Step 8.1] + +## Fix tally + +- REAL_PROD fixed inline: N +- KNOWN_TEMP (DevIndexBadge) — verified ignored via hideElements: N +- STRUCTURAL_VUETIFY (.v-overlay-container) — documented: N +- MIGRATED_KNOWN (empty-table-header from `c524227`) — verified closed: N + +## Verdict: 🟢 GREEN / 🟡 YELLOW +``` + +--- + +## Task 10 — Final regression sweep (Phase 14-style) + +**Files:** + +- No file changes — verification only. + +### Step 10.1 — Run all gates + +```bash +cd "/c/моя/проекты/портал crm/Документация/app" && ./vendor/bin/pest --parallel +``` + +Expected: 742/739/3sk/0. + +```bash +cd "/c/моя/проекты/портал crm/Документация/app" && npx vitest run --no-coverage +``` + +Expected: 91 files / 736 / 3sk / 0. + +```bash +cd "/c/моя/проекты/портал crm/Документация/app" && npx vite build +``` + +Expected: ≤3s. + +```bash +cd "/c/моя/проекты/портал crm/Документация" && npm run links +``` + +Expected: 0 errors. + +```bash +cd "/c/моя/проекты/портал crm/Документация" && ./bin/gitleaks.exe detect --source . --redact --log-level info +``` + +Expected: 0 leaks. + +```bash +cd "/c/моя/проекты/портал crm/Документация" && npm run a11y +``` + +Expected: full pass on all 21+ URLs after Task 7 fixes (or documented exemptions). + +--- + +## Task 11 — Final commit + push + +### Step 11.1 — Stage doc-only changes from Tasks 8+9 + +```bash +cd "/c/моя/проекты/портал crm/Документация" +git add pa11y.config.json docs/audit-baseline-pa11y.md docs/superpowers/audits/2026-05-14-a11y-rescan-findings.md docs/superpowers/plans/2026-05-14-a11y-rescan-live-portal.md +``` + +NB: code-fix commits from Task 7 are already individually committed. + +### Step 11.2 — Commit doc bundle + +```bash +git commit -m "$(cat <<'EOF' +docs(a11y): extend Pa11y scope to 14 authenticated routes + audit findings + +Closes Audit #3 Phase 7 «authenticated pages out of scope» clause from +deferred-fixes sprint Task 1. + +- pa11y.config.json: +14 authenticated URLs via per-URL actions login flow +- docs/audit-baseline-pa11y.md: «Authenticated rescan 2026-05-14» section +- docs/superpowers/audits/2026-05-14-a11y-rescan-findings.md: full findings + +Final tallies: URLs / violations / fixes inline. + +Co-Authored-By: Claude Opus 4.7 (1M context) +EOF +)" +``` + +### Step 11.3 — Push + +```bash +git push origin main +``` + +Pre-push hooks: gitleaks-full-history + lychee-links must pass. + +--- + +## Self-review + +**Spec coverage:** + +- ✅ Re-verify guest baseline (Task 1) +- ✅ Login flow discovery (Task 2) +- ✅ Pa11y actions config (Task 3) +- ✅ Run + tally (Task 4) +- ✅ Categorize findings (Task 5) +- ✅ axe-core cross-validation (Task 6) +- ✅ Fix real prod findings (Task 7) +- ✅ Update baseline doc (Task 8) +- ✅ Audit findings document (Task 9) +- ✅ Final regression (Task 10) +- ✅ Commit + push (Task 11) + +**Placeholder scan:** + +- Task 3 references `` etc. — these ARE filled in once Task 2 produces the selectors. Acceptable per workflow. +- Task 7 file paths are TBD — fundamentally cannot be filled in upfront because they depend on Task 5 findings. The procedure (identify source → minimal change → verify → commit) is fully specified. +- Task 8/9 violation tables are template skeletons — populated as Task 4 produces data. + +**Type consistency:** + +- All Pa11y config blocks share same `defaults` shape — consistent with current `pa11y.config.json`. +- All git commands use absolute path pattern — quirk #79 compliant. +- All `cd "/c/моя/проекты/портал crm/Документация"` invocations use full absolute path — no double-cd risk. + +--- + +## Execution choice + +Plan complete and saved to `docs/superpowers/plans/2026-05-14-a11y-rescan-live-portal.md`. Two execution options: + +1. **Inline Execution (recommended for this task)** — sequential in this session with checkpoints. Task 6 (Playwright MCP) and Task 7 (fix iteration) need orchestrator decisions per-finding — subagent dispatch would fragment context. +2. **Subagent-Driven** — alternative for parallelism, but most tasks have inter-task dependencies (Task 2 selectors → Task 3 config → Task 4 run → Task 5 categorize → Task 7 fix per finding). Linear flow fits better. + +**Recommendation:** Inline Execution.