# 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: <selector>
- password input: <selector>
- submit button: <selector>
- 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 <SELECTOR_FROM_TASK_2> to be visible",
        "set field <EMAIL_SELECTOR> to admin@demo.local",
        "set field <PASSWORD_SELECTOR> to password",
        "click element <SUBMIT_SELECTOR>",
        "wait for path to be /dashboard",
        "navigate to http://localhost:8000/dashboard",
        "wait for element <DASHBOARD_LANDMARK> to be visible"
      ]
    }
  ]
}
```

Replace `<SELECTOR_FROM_TASK_2>` 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/<route>`
- [ ] `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 "<full-url>"
```

- [ ] Verify violation gone, no new ones introduced.

### Step 7.2 — Commit per fix

```bash
git add <files>
git commit -m "fix(a11y): <one-line summary>"
```

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: <N> URLs / <V> violations / <F> fixes inline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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 `<SELECTOR_FROM_TASK_2>` 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.
