Files
portal/docs/superpowers/plans/2026-05-14-audit3-deferred-fixes.md
T
Дмитрий 8ba9c55724 docs(plan): Audit #3 deferred fixes sprint plan
25 deferred findings (1 P1 + 11 P2 + 14 P3) → 4 task batches:

1. P1 Pa11y scope migration to live Vue app
2. P2 dead code + dev hygiene (knip findings + DemoSeeder + schema header)
3. P2 coverage debt (ReminderDialog + AdminLayout + api/admin via TDD)
4. P3 tooling + structural mini-fixes

Plan: docs/superpowers/plans/2026-05-14-audit3-deferred-fixes.md
Source audit: docs/superpowers/audits/2026-05-14-portal-full-audit-report.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 08:24:49 +03:00

31 KiB
Raw Blame History

Audit #3 Deferred Fixes 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: Close all 25 deferred findings from Portal Full Audit #3 (report) — 1 P1 + 11 P2 + 14 P3 — without regressions.

Architecture: Tasks 1→2→3→4 sequentially (each isolates the surface it modifies). Inside Task 3 — 3 parallel subagent dispatches (one per coverage target — fully isolated). Atomic commit per logical change. Final regression suite (Pest --parallel + Vitest + Vite build + gitleaks + lychee) gates push.

Tech Stack: Laravel 13.7 + Vue 3.5 + Vuetify 3.12 + Vitest 4.1 + @vue/test-utils + Pest 4.7 + pa11y-ci 4.1 + Playwright MCP + Lefthook + GitHub Actions

Branch: work directly on main (current 🟢 GREEN baseline f9d2452); each commit small + atomic; push at the very end after Phase 14-style regression check.

Out of scope: Sentry SDK install (blocked by Б-1), advisory TODO cleanup at Б-1 closure, F-BUN-02/03 Vuetify-structural (carryforward, no fix path until upstream change), F-COV-04..07 P3 (acknowledged as observational — router/index.ts 24+ lazy factories inflate denominator, no real fix; Vuetify plugin needs visual tests).


Task 1 — P1 Pa11y scope migration to live Vue app

Why: Pa11y config указывает на liderra_v8_handoff/concepts/v8_*.html (handoff prototypes), не на live Vue. Audit #2 baseline «0 errors» был неточен — реальный портал НЕ покрыт. Сегодня axe-core via Playwright показал 0 production a11y issues, но Pa11y нужно перевести на актуальную мишень для CI safety net.

Files:

  • Create: app/.github/workflows/a11y.yml
  • Create: pa11y-handoff.config.json (historical handoff baseline, opt-in via a11y:handoff)
  • Modify: pa11y.config.json (live Vue app URLs)
  • Modify: package.json (add a11y:handoff script; ensure a11y script unchanged invocation but new target)
  • Reference: app/routes/web.php (SPA routes catalogue: 26 Route::view + fallback)

Step 1.1 — Read current handoff baseline & freeze as historical config

  • Read pa11y.config.json to capture exact handoff URL list (3 entries: v8_login.html, v8_dashboard.html, v8_deals.html).
  • Copy that content (with same defaults block) to a new file pa11y-handoff.config.json.
cp pa11y.config.json pa11y-handoff.config.json

Expected: pa11y-handoff.config.json exists, contains 3 handoff URLs verbatim.

Step 1.2 — Rewrite pa11y.config.json to target live Vue app (guest pages — MVP scope)

  • Replace pa11y.config.json contents with the live-app config below. Authenticated pages deferred to Step 1.7 (require Pa11y actions login flow — adds complexity; guest pages first).
{
  "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/login",
      "screenCapture": "./bin/a11y-screenshots/live-01-login.png"
    },
    {
      "url": "http://localhost:8000/register",
      "screenCapture": "./bin/a11y-screenshots/live-02-register.png"
    },
    {
      "url": "http://localhost:8000/forgot",
      "screenCapture": "./bin/a11y-screenshots/live-03-forgot.png"
    },
    {
      "url": "http://localhost:8000/2fa",
      "screenCapture": "./bin/a11y-screenshots/live-04-twofactor.png"
    },
    {
      "url": "http://localhost:8000/recovery",
      "screenCapture": "./bin/a11y-screenshots/live-05-recovery.png"
    },
    {
      "url": "http://localhost:8000/403",
      "screenCapture": "./bin/a11y-screenshots/live-06-403.png"
    },
    {
      "url": "http://localhost:8000/500",
      "screenCapture": "./bin/a11y-screenshots/live-07-500.png"
    }
  ]
}

Rationale on hideElements: scoping out (a) .dev-index-badge + .dev-index-num — TEMPORARY DevIndexBadge feature (project_dev_indices.md — заказчик «уберём в конечном релизе»); (b) .v-overlay-container — Vuetify portal container is empty when overlays closed but axe-core/Pa11y flags it as missing region landmark — known Vuetify structural pattern.

Step 1.3 — Add a11y:handoff script + ensure a11y invokes new config

  • Modify package.json — add new script + keep existing a11y invocation unchanged (it already points to pa11y.config.json which we just repointed).

Apply edit (in package.json scripts block, insert after current a11y line):

"a11y": "pa11y-ci --config pa11y.config.json",
"a11y:handoff": "pa11y-ci --config pa11y-handoff.config.json",

Step 1.4 — Start dev-server, run live baseline locally

  • In one terminal, start dev-server:
cd app
php artisan serve --port=8000
  • In second terminal, ensure tenant DB is seeded (DemoSeeder will be auto-run after Task 2.4, for now run manually if Step 1 executes first):
cd app
php artisan migrate:fresh --seed
php artisan db:seed --class=DemoSeeder
  • In third terminal, run Pa11y:
npm run a11y

Expected: 7 URLs scanned. Capture exit code + per-URL violation count.

Step 1.5 — Resolve violations: either fix code OR document explicit ignore

  • If color-contrast violations on auth pages → fix in app/resources/js/layouts/AuthLayout.vue or specific view CSS (likely none expected; brand palette OKLCH-tested).
  • If structural violations (landmarks missing on guest pages) → fix in respective views/auth/*View.vue (add <main> / <header> semantics if missing).
  • Any remaining unfixable (Vuetify structural) — add to pa11y.config.json per-URL ignore array, with comment in JSON ref-document or inline if format allows. Note: JSON doesn't allow comments — track ignored selectors in docs/audit-baseline-pa11y.md (Step 1.6).

Step 1.6 — Document baseline + ignore decisions

  • Create docs/audit-baseline-pa11y.md capturing:
    • Date of first live baseline
    • Per-URL violation summary at baseline
    • Per-violation: status (fixed / ignored-permanent / ignored-temp DevIndexBadge / ignored-structural Vuetify) + reference
# Pa11y Live Baseline — YYYY-MM-DD

> First live-Vue baseline after Pa11y scope migration (Audit #3 P1 closure).

## URLs scanned

| URL | Violations baseline | Status |
|---|---|---|
| /login | 0 | ✅ clean |
| /register | 0 | ✅ clean |
| ... | | |

## Ignored selectors (rationale)

| Selector | Why ignored |
|---|---|
| `.dev-index-badge`, `.dev-index-num` | TEMPORARY DevIndexBadge feature; removed at final release per `project_dev_indices.md` |
| `.v-overlay-container` | Vuetify portal container — empty when overlays closed; structural pattern, not a real a11y issue |

Step 1.7 — Add CI workflow (.github/workflows/a11y.yml)

  • Create app/.github/workflows/a11y.yml to run Pa11y in CI with dev-server lifecycle:
name: Accessibility (Pa11y live)

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  a11y:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install JS deps
        run: npm ci

      - name: Install PHP deps
        working-directory: app
        run: composer install --no-progress --no-interaction --prefer-dist

      - name: Setup database
        working-directory: app
        run: |
          cp .env.example .env
          php artisan key:generate
          php artisan migrate:fresh --seed
          php artisan db:seed --class=DemoSeeder

      - name: Start dev-server
        working-directory: app
        run: php artisan serve --port=8000 &

      - name: Wait for dev-server
        run: npx wait-on http://localhost:8000

      - name: Run Pa11y
        run: npm run a11y

Note: wait-on may not be installed; add to devDependencies if needed. Alternatively, busy-loop with curl in CI script.

Step 1.8 — Verify CI workflow locally (act if available, otherwise visual review only)

  • git status shows: pa11y.config.json modified, pa11y-handoff.config.json new, package.json modified, docs/audit-baseline-pa11y.md new, app/.github/workflows/a11y.yml new.
  • Run final Pa11y locally: npm run a11y → exit 0.
  • Run historical handoff: npm run a11y:handoff → still scans 3 handoff URLs (unchanged baseline — 10 contrast violations).

Step 1.9 — Commit

git add pa11y.config.json pa11y-handoff.config.json package.json \
        docs/audit-baseline-pa11y.md app/.github/workflows/a11y.yml
git commit -m "$(cat <<'EOF'
feat(a11y): migrate Pa11y scope from handoff prototypes to live Vue app

Closes F-A11Y-PA11Y-SCOPE-01 (Audit #3 sole P1).

- pa11y.config.json now targets http://localhost:8000/<route> for 7 guest pages
  (login, register, forgot, 2fa, recovery, 403, 500)
- pa11y-handoff.config.json preserves historical 3-URL handoff baseline
  (opt-in via `npm run a11y:handoff`)
- DevIndexBadge + Vuetify .v-overlay-container hidden globally via
  hideElements (TEMPORARY feature + structural Vuetify pattern)
- New `.github/workflows/a11y.yml` runs Pa11y in CI with dev-server lifecycle
  (php artisan serve + wait-on http://localhost:8000)
- New docs/audit-baseline-pa11y.md captures first live baseline + ignore rationale

Related: Audit #3 report, findings.md F-A11Y-PA11Y-SCOPE-01.
EOF
)"

Task 2 — P2 dead code + dev hygiene batch

Why: 5 documented P2 deferred items — все низкорисковые, удаление/добавление без изменения публичного API.

Files:

  • Delete: app/resources/js/views/admin/AdminPlaceholderView.vue
  • Modify: app/package.json (remove concurrently)
  • Modify: app/resources/js/api/admin.ts (remove 4 unused exported types)
  • Modify: app/resources/js/api/notifications.ts (remove 1 unused type)
  • Modify: app/resources/js/api/reports.ts (remove 5 unused types)
  • Modify: app/resources/js/composables/mockBilling.ts (remove 1 unused type)
  • Modify: app/resources/js/composables/useStatusPill.ts (remove 1 unused type)
  • Modify: app/database/seeders/DatabaseSeeder.php (env-conditional DemoSeeder::class)
  • Modify: db/schema.sql (header line 4: «62 базовые таблицы» → «63 базовые таблицы + 12 партиций»)

Step 2.1 — Confirm AdminPlaceholderView is truly unreferenced + delete

  • Grep one more time before delete (paranoia):
cd app
git grep -n "AdminPlaceholderView"

Expected: matches only inside AdminPlaceholderView.vue itself (no router/import references).

  • Delete the file:
git rm app/resources/js/views/admin/AdminPlaceholderView.vue

Step 2.2 — Remove concurrently from app/package.json

  • Verify in app/package.json line ~27 (devDependencies block) that concurrently@^9.0.1 is present.
  • Verify no script invokes concurrently:
grep -n concurrently app/package.json

Expected: only the devDependencies entry, no scripts.

  • Uninstall:
cd app
npm uninstall concurrently

Expected: package.json + package-lock.json updated; concurrently gone from node_modules.

Step 2.3 — Remove 12 unused exported types

For each file, locate the named export (search by export type <Name> or export interface <Name>) and delete that block. If the type is part of a barrel or used in a re-export, also remove the re-export.

  • app/resources/js/api/admin.ts: remove AdminTenantsStats, ApiTenantMetrics, ApiAdminBillingSummary, ApiAdminIncidentsSummary.
  • app/resources/js/api/notifications.ts: remove NotificationEvent.
  • app/resources/js/api/reports.ts: remove ApiReportType, ApiReportFormat, ApiReportParameters, ReportCounts, ReportQuota.
  • app/resources/js/composables/mockBilling.ts: remove TxType.
  • app/resources/js/composables/useStatusPill.ts: remove StatusPillSlug.

After each file's edit, run:

cd app
npx vue-tsc --noEmit

Expected: 0 type errors. If errors appear (a removed type is actually used internally), restore the type and document in finding.

Step 2.4 — Make DemoSeeder auto-runnable but only in non-production

  • Modify app/database/seeders/DatabaseSeeder.php:
<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    use WithoutModelEvents;

    /**
     * Seed the application's database.
     *
     * PricingTierSeeder runs in all environments (production needs the 7-tier
     * config bootstrapped). DemoSeeder runs only in local/testing — populates
     * a demo tenant + admin user + 3 projects + ~14 demo deals for dev/UI smoke.
     */
    public function run(): void
    {
        $this->call(PricingTierSeeder::class);

        if (app()->environment('local', 'testing')) {
            $this->call(DemoSeeder::class);
        }
    }
}
  • Verify locally:
cd app
php artisan migrate:fresh --seed
php artisan tinker --execute='echo \App\Models\User::count();'

Expected: User count ≥ 1 (DemoSeeder created admin@demo.local).

Step 2.5 — Fix schema.sql header drift (62 → 63 base tables)

  • Read db/schema.sql line 4 — current text:

    -- Метрики: 62 базовые таблицы + 12 партиций / 117 индексов / 39 RLS-политик / 5 функций / 13 триггеров

  • Replace with:

    -- Метрики: 63 базовые таблицы (61 regular + 2 partitioned parents: deals + supplier_lead_costs) + 12 партиций / 117 индексов / 39 RLS-политик / 5 функций / 13 триггеров

Apply via Edit tool, exact old_stringnew_string.

Step 2.6 — Also reflect drift in CLAUDE.md §2 + §0 + §8 baseline numbers

  • Update CLAUDE.md references to «62 базовые таблицы» (3 occurrences — §0 «Источник истины» row «Схема БД», §2 «Стек проекта» row «БД», §8 «Self-review триггеры» row db/schema.sql).

NB on CLAUDE.md edit channel: per CLAUDE.md §5 п.10, CLAUDE.md modifications go ONLY through claude-md-management plugin. This step is invoked separately — see Step 2.8.

Step 2.7 — Verify nothing broken

  • Run from app/:
cd app
npx vue-tsc --noEmit
npx eslint resources/js/**/*.{ts,vue}
npx vite build
./vendor/bin/pest --parallel

Expected: all green. Vitest will be run in Final verification (Task end).

Step 2.8 — Commit (code part)

git add app/resources/js/views/admin/AdminPlaceholderView.vue \
        app/package.json app/package-lock.json \
        app/resources/js/api/admin.ts \
        app/resources/js/api/notifications.ts \
        app/resources/js/api/reports.ts \
        app/resources/js/composables/mockBilling.ts \
        app/resources/js/composables/useStatusPill.ts \
        app/database/seeders/DatabaseSeeder.php \
        db/schema.sql
git commit -m "$(cat <<'EOF'
chore(cleanup): dead code removal + DemoSeeder auto-run + schema header drift

Closes Audit #3 P2 batch (knip dead exports/components, DemoSeeder hygiene,
schema header drift).

- Remove AdminPlaceholderView.vue (unreferenced placeholder view)
- Uninstall concurrently devDep (no scripts invoked it)
- Remove 12 unused exported types in api/{admin,notifications,reports}.ts
  and composables/{mockBilling,useStatusPill}.ts
- DatabaseSeeder::run() now calls DemoSeeder in local+testing envs
  (dev `migrate:fresh --seed` produces demo tenant + admin@demo.local)
- schema.sql header: «62 базовые таблицы» → «63 (61 regular + 2 partitioned)»

Verification: vue-tsc 0 / ESLint 0 / Vite build OK / Pest --parallel green.
EOF
)"

Step 2.9 — CLAUDE.md update via plugin

Invoke /claude-md-management:claude-md-improver skill to update CLAUDE.md references «62 базовые таблицы» → «63 базовые таблицы (61 regular + 2 partitioned parents)» in §0 / §2 / §8. Skill produces separate commit per its conventions.


Task 3 — P2 coverage debt (3 files, parallel subagents)

Why: Carryforward F-COV-01/02/03 from Audit #2 — three files with low coverage represent regression risk if changed without tests.

Parallelism: Each file is fully isolated (different test file, different source file). Dispatch 3 subagents in parallel via superpowers:dispatching-parallel-agents.

Files (per subagent):

Subagent Source file Test file Target coverage
A app/resources/js/components/reminders/ReminderDialog.vue app/tests/Frontend/components/reminders/ReminderDialog.spec.ts (new) Stmts ≥ 60%
B app/resources/js/layouts/AdminLayout.vue app/tests/Frontend/layouts/AdminLayout.spec.ts (new) Stmts ≥ 60%
C app/resources/js/api/admin.ts app/tests/Frontend/api/admin.spec.ts (extend existing or create) Stmts ≥ 80%

Step 3.1 — Pre-flight per subagent: read source + existing tests

Before launching subagents, the orchestrator reads the 3 source files + checks for existing test scaffolds. This enables targeted briefing per subagent.

  • Read app/resources/js/components/reminders/ReminderDialog.vue
  • Read app/resources/js/layouts/AdminLayout.vue
  • Read app/resources/js/api/admin.ts
  • Check existing tests: app/tests/Frontend/components/reminders/, app/tests/Frontend/layouts/, app/tests/Frontend/api/

Step 3.2 — Dispatch 3 parallel subagents (each writes TDD-style)

Each subagent runs superpowers:test-driven-development skill. Prompts include:

  • Exact source file path + content excerpt
  • Existing test patterns to mirror (from co-located neighbour tests)
  • Coverage target
  • Vitest + @vue/test-utils API (Vuetify mounting helper from app/tests/Frontend/setup.ts)

Subagent A briefing template:

Source: app/resources/js/components/reminders/ReminderDialog.vue (full content)
Test: NEW app/tests/Frontend/components/reminders/ReminderDialog.spec.ts
Target: Stmts ≥ 60% (currently 0%)
Approach: TDD. Mount with vuetify stubs, verify dialog open/close, form submit,
  validation states, emit('save')/emit('close') events. Mock store/api with vi.mock.
Pattern: mirror app/tests/Frontend/components/deals/NewDealDialog.spec.ts shape.

Subagent B briefing template:

Source: app/resources/js/layouts/AdminLayout.vue (full content)
Test: NEW app/tests/Frontend/layouts/AdminLayout.spec.ts
Target: Stmts ≥ 60% / Branch ≥ 40% / Funcs ≥ 40% (currently 9.09% / 0% / 0%)
Approach: TDD. Mount with vue-router stub + Pinia, verify sidebar nav renders,
  active route highlight, logo + brand link, breadcrumb container.
Pattern: mirror app/tests/Frontend/layouts/AppLayout.spec.ts.

Subagent C briefing template:

Source: app/resources/js/api/admin.ts (full content)
Test: app/tests/Frontend/api/admin.spec.ts (new or extend)
Target: Stmts ≥ 80% / Funcs ≥ 80% (currently 11.53% / 27.27%)
Approach: TDD. Mock axios via vi.mock, test each exported function:
  request URL+payload, response transformer, error path (401/422/500).
Pattern: mirror app/tests/Frontend/api/deals.spec.ts shape.

Each subagent is required to:

  • Write failing test first
  • Run to confirm fail
  • Implement (or no implementation needed — testing existing code)
  • Verify pass
  • Report coverage delta (npx vitest run --coverage <path> before/after)
  • Return ONLY the new test file content + raw coverage numbers (raw output, not summary, per economy 0% rule)

Step 3.3 — Orchestrator review of 3 subagent outputs

  • Verify each test file was created with proper structure
  • Run combined coverage check:
cd app
npx vitest run --coverage

Expected: all 3 target files at or above threshold; total Stmts ≥ 79% (was 78.64%).

Step 3.4 — Commit (one commit per subagent output OR one combined commit; choose combined for atomicity of «coverage debt closure»)

git add app/tests/Frontend/components/reminders/ReminderDialog.spec.ts \
        app/tests/Frontend/layouts/AdminLayout.spec.ts \
        app/tests/Frontend/api/admin.spec.ts
git commit -m "$(cat <<'EOF'
test(coverage): close F-COV-01/02/03 — ReminderDialog/AdminLayout/api-admin

Closes Audit #2+#3 P2 carryforward triplet.

- ReminderDialog.vue: 0% → ≥60% Stmts (dialog/form/emit lifecycle covered)
- AdminLayout.vue: 9.09% → ≥60% Stmts (sidebar nav + active route + logo)
- api/admin.ts: 11.53% → ≥80% Stmts (8 endpoints + error paths)

Total Vitest Stmts coverage ↑ from 78.64% to <new total> (+<delta> pp).

Approach: TDD via @vue/test-utils + Vuetify stubs + vi.mock axios — no
production code changed, only test infrastructure added.
EOF
)"

Task 4 — P3 tooling + structural mini-fixes

Files:

  • Create: app/.knip-config-cleanup.ts (or modify existing app/knip.config.ts)
  • Modify: package.json (add npm overrides for lodash transitive)
  • Modify: app/resources/js/views/admin/AdminTenantsView.vue (table header)
  • Modify: app/resources/js/views/admin/AdminBillingView.vue (if same pattern)
  • Modify: app/tests/Frontend/setup.ts or test-specific config (axe-core rule region whitelist for .v-overlay-container)
  • Decision document: docs/superpowers/decisions/2026-05-14-pgformatter-perl-decision.md (capture chosen path)

Step 4.1 — pgFormatter perl carryforward — decision document only

Decision: keep [FIX-DEFER] (Q.HARD.002 from Audit #2). Installing perl on Windows dev machine is one-line choco install strawberryperl, but adds toolchain weight. Action: document explicit decision + alternative paths in docs/superpowers/decisions/2026-05-14-pgformatter-perl-decision.md:

  • Create decision doc:
# Decision — pgFormatter on Windows dev (Q.HARD.002)

> **Date:** 2026-05-14
> **Status:** carryforward [FIX-DEFER]
> **Audits:** raised in #2, confirmed in #3

## Context

`npm run format:sql:check` falls because `perl` isn't on the PATH of the
Windows dev VM. pgFormatter lives in `bin/pgFormatter/pg_format` (Perl
script).

## Options

1. **`choco install strawberryperl`** — adds ~150 MB toolchain; works.
2. **Replace with `sqlfluff`** — Python-based; needs Python toolchain
   (already partially present); different format opinions than pgFormatter,
   would force a re-format of `db/schema.sql` (~6284 lines).
3. **Use `pg_format` via Docker** — adds Docker dep; we don't currently use
   Docker on this VM (project_phase1_strategy.md — native Windows stack).
4. **Drop automated SQL formatting** — accept manual hygiene + squawk
   (linter) covers correctness.

## Decision

Carry forward to Б-1 closure cycle. Production SQL hygiene is covered by
squawk (lint). pgFormatter is purely cosmetic in our flow.

Step 4.2 — npm overrides for lodash transitive (pa11y-ci dev-only vuln)

  • Modify root package.json — add overrides block:
{
  ...,
  "overrides": {
    "pa11y-ci": {
      "lodash": "^4.17.21"
    }
  }
}
  • Run npm install to apply override.
  • Verify: npm audit --omit=optional — lodash entry should be gone or downgraded severity to none.

Step 4.3 — Add table header label for actions column

  • Read app/resources/js/views/admin/AdminTenantsView.vue — locate the VDataTable headers definition.
  • Update the actions column header (8-th column) from empty title to visually hidden label:

If headers are defined as object array { title: '', key: 'actions', ... }, change to:

{ title: 'Действия', key: 'actions', sortable: false, headerProps: { class: 'sr-only-header' } }

Add CSS class:

.sr-only-header .v-data-table-header__content {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
  • Same change for AdminBillingView.vue actions column (if pattern present).
  • Verify with axe-core (Playwright run):
cd app
npm run test:a11y -- /admin/tenants

Expected: 0 empty-table-header violations.

Step 4.4 — axe-core test config: whitelist Vuetify .v-overlay-container region rule

  • Find axe-core invocation in app/tests/Frontend/ (smoke test that invokes axe). If it's a Playwright/Pest dual-runtime test, locate the run config.
  • Add exclusion to axe-core options:
axe.run({
  exclude: [['.v-overlay-container']],
  ...
})

OR set region rule disabled for that selector via disableOtherRules: false + per-rule selector.

  • Verify: npm run test:a11y exits 0.

Step 4.5 — Clean up knip.config.ts stale hints (P3 item)

  • Read app/knip.config.ts — locate ignore: ['tests/**'] and ignoreDependencies: ['@vue/test-utils', 'jsdom', 'vitest'].
  • Remove those entries.
  • Add entry: [..., 'histoire.config.ts'] to silence histoire.setup.ts + @histoire/plugin-vue FPs.
  • Re-run: cd app && npx knip — expected: clean output (12 dead exports already gone in Task 2, FPs silenced).

Step 4.6 — Commit

git add docs/superpowers/decisions/2026-05-14-pgformatter-perl-decision.md \
        package.json package-lock.json \
        app/resources/js/views/admin/AdminTenantsView.vue \
        app/resources/js/views/admin/AdminBillingView.vue \
        app/tests/Frontend/setup.ts \
        app/knip.config.ts
git commit -m "$(cat <<'EOF'
chore(p3): close P3 tooling and structural mini-fixes

Closes:
- pgFormatter perl carryforward — decision doc only (Q.HARD.002 stays deferred)
- npm overrides for lodash transitive (pa11y-ci dev-only vuln)
- AdminTenantsView/AdminBillingView actions column header sr-only label
  (closes axe empty-table-header on /admin/tenants)
- axe-core test config whitelists .v-overlay-container region rule
  (Vuetify structural pattern, not real a11y issue)
- knip.config.ts: remove 4 stale hints + add histoire.config.ts to entry
EOF
)"

Final verification — full regression sweep

Mirror Phase 14 of Audit #3 — same suite, same thresholds.

Step F.1 — Pest --parallel

cd app
./vendor/bin/pest --parallel

Expected: 742/739/3sk/0 failed (or improved). 0 regressions.

Step F.2 — Vitest

cd app
npx vitest run

Expected: 88+ files / 683+ passed (3+ new test files from Task 3) / 3 skipped / 0 failed.

Step F.3 — Vite build

cd app
npx vite build

Expected: exit 0, build time < 3.5s (Audit #3 baseline 2.21s).

Step F.4 — gitleaks full history

./bin/gitleaks.exe detect --source . --redact -v --log-level info

Expected: 0 leaks across now ~440+ commits.

npm run links

Expected: 319+ OK / 0 broken.

Step F.6 — vue-tsc + ESLint + Prettier + markdownlint + cspell sanity

cd app
npx vue-tsc --noEmit
npx eslint resources/js/**/*.{ts,vue}
npx prettier --check resources/js/**/*.{ts,vue,css}
cd ..
npx markdownlint-cli2 "docs/**/*.md" "*.md"
npx cspell --no-progress "docs/**/*.md" "*.md"

All expected: exit 0.

Step F.7 — Cleanup package-lock.json CRLF flap (cosmetic — only if dirty)

git checkout -- package-lock.json

(Safe — git diff empty; pure line-ending normalisation.)


Push to origin/main

Step P.1 — Final git status sanity

git status
git log --oneline origin/main..HEAD

Expected: clean working tree; ~47 new commits ahead of origin/main.

Step P.2 — Push

git push origin main

Pre-push hooks (lefthook): gitleaks-full-history + lychee + (other configured). Must all pass.

Step P.3 — Memory updates

Invoke /claude-md-management:revise-claude-md to capture this session's learnings into memory:

  • project_state.md — bump HEAD to new commit
  • project_full_audit_2026-05-14.md — append «Audit #3 deferred fixes closure session» section
  • feedback_environment.md — capture any new quirks discovered during Task 1 (Pa11y CI lifecycle, dev-server bootstrap) or Task 4 (npm overrides syntax)

Step P.4 — Открытые_вопросы registry bump

Reflect closures in docs/Открытые_вопросы_v8_3.md if any of the deferred items had a Q-prefixed code. (Audit #3 documented 0 BLOCKED Q-items — but technical-debt closures still benefit from registry note.)


Self-review checklist

Spec coverage:

  • P1 Pa11y scope → Task 1
  • P2 AdminPlaceholderView → Task 2.1
  • P2 concurrently → Task 2.2
  • P2 12 dead exports → Task 2.3
  • P2 DemoSeeder hygiene → Task 2.4
  • P2 schema header drift → Task 2.5 (+ CLAUDE.md 2.6/2.9)
  • P2 F-COV-01/02/03 → Task 3
  • P2 GITHUB_TOKEN in cache — NOT addressed (already gitignored, mitigations in place per Audit #3 — documentation-only, no code action; skip)
  • P2 Sentry SDK — OUT OF SCOPE (blocked by Б-1)
  • P2 Handoff prototypes contrast — addressed by relocating to pa11y-handoff.config.json (opt-in)
  • P3 knip config → Task 4.5
  • P3 npm lodash → Task 4.2
  • P3 empty-table-header → Task 4.3
  • P3 region landmarks → Task 4.4
  • P3 pgFormatter perl → Task 4.1 (decision doc only)
  • P3 F-COV-04..07 — OUT OF SCOPE (observational, no actionable fix per audit reasoning)
  • P3 generic-api-key FPs in vendor → addressed indirectly by gitleaks allowlist in .gitleaks.toml (optional Task 4 micro-step; skip if already present)
  • P3 npm lodash dev-only → Task 4.2
  • P3 F-BUN-01/02/03 — OUT OF SCOPE (Vuetify structural)
  • P3 advisory TODO at Б-1 closure — OUT OF SCOPE

Placeholder scan:

  • 0 «TBD» / 0 «implement later» / 0 «similar to Task N». All code blocks contain full content.

Type consistency:

  • All Vue test-utils signatures consistent. All Pest command invocations consistent. All git command shapes consistent.

Execution choice

Plan saved to docs/superpowers/plans/2026-05-14-audit3-deferred-fixes.md.

Two execution options per superpowers:writing-plans:

  1. Subagent-Driven (recommended) — fresh subagent per task, orchestrator review between tasks, fast iteration. Task 3 already designed for parallel subagent dispatch.
  2. Inline Execution — all tasks in this session via superpowers:executing-plans with checkpoint reviews.

Recommendation for this work: Subagent-Driven for Task 3 (3 parallel) + Inline for Tasks 1/2/4 (sequential, shared file state across steps). Mixed mode.