axios.patch теперь в try/catch с extractErrorMessage() helper. Per-row
ошибки — reactive errorMessages: Record<number, string> отображаются как
v-icon mdi-alert-circle с v-tooltip рядом с кнопкой «Сохранить».
Success — v-snackbar (3s timeout, color=success, bottom-right) с именем
поставщика.
Retry на той же строке очищает предыдущий error перед новым axios.patch.
load() тоже обёрнут — fetchError ref + v-alert warning сверху таблицы.
+3 Vitest specs (save error / save success / retry clears error).
Регрессий 0.
Closes audit ID G2 from docs/superpowers/specs/2026-05-15-portal-audit-design.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-review fixes для commit 72a0064 (G1 AdminPricingTiersView errors):
I-1 (mock leak risk): Добавлен afterEach(() => vi.clearAllMocks()) в
новый describe block. Раньше axios.isAxiosError.mockReturnValue(true)
оставался активным после run'а нового describe. Сейчас нет других
тестов после G1 describe в файле — но future-proof против перестановки
test order.
I-2 (coverage gap): Оба success теста (submit + confirmDelete) теперь
assert vm.successToastOpen === true. Раньше тест мог пройти, если
кто-то забыл successToastOpen.value = true в impl — message set, но
snackbar не открыт. Now covered.
Tests 9/9. Регрессий 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
axios.post/delete теперь обёрнуты в try/catch с extractErrorMessage()
хелпером из api/client.ts (same pattern as AdminSystemView.vue:32-45).
errorMessage отображается в v-alert (closable, type=error, tonal),
successMessage — в v-snackbar (color=success, 4s timeout).
На failed submit диалог остаётся открытым чтобы пользователь мог
исправить и повторить (UX-pattern). saving=false гарантированно
сбрасывается в finally.
+4 Vitest specs (submit error / submit success / delete error / delete success).
Регрессий 0.
Closes audit ID G1 from docs/superpowers/specs/2026-05-15-portal-audit-design.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
window.alert блокирует UI thread, не accessible (a11y), breaks браузерный
automation (Playwright/Selenium). Заменено на v-snackbar (timeout 6s,
color warning, location bottom-right, кнопка «Закрыть»). Текст идентичен:
«Применено: N. Пропущено: M (конфликт с уже доставленными лидами).»
+2 Vitest specs (snackbar opens / snackbar НЕ opens at skipped=0).
window.confirm для pause/resume/archive намеренно оставлен — это
deliberate blocking прерывание для деструктивных операций (UX-pattern).
Closes audit ID C5 from docs/superpowers/specs/2026-05-15-portal-audit-design.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-review fixes для commit 9068005 (C4 KanbanView DnD persist):
I-2 (test coverage gap): Revert test «onColumnChange reverts...» теперь
seed'ит deal в dealsByStatus['hot'] до вызова onColumnChange (имитируя
vuedraggable mutation pre-event). После failed transition — assert
карточка удалена из hot + восстановлена в new. Раньше array-revert
branch в KanbanView.vue:80-87 (splice + push) имел 0 test coverage —
findIndex возвращал -1, splice silent. Теперь coverage 100%.
I-3 (stale JSDoc): File-header comment в KanbanView.vue lines 7-16
обновлён — описывает actual behavior после Task 2 (optimistic + API call
+ revert). Раньше явно врал «не входит в этот коммит: PATCH /api/deals/
{id}» когда POST /api/deals/transition уже реализован.
Регрессий 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drag-drop между колонками теперь сохраняется в БД через существующий
DealBulkActionController@transition endpoint (single-element массив).
Optimistic UI update (statusSlug меняется сразу) + revert-on-fail с
toast «Не удалось переместить — восстановлен исходный статус».
Без auth.user.tenant_id (dev/demo без login) — local-only mode, API не
зовётся (graceful degradation).
+3 Vitest specs в KanbanView.spec.ts (success / revert / no-auth skip).
Pest covered by existing DealTransitionTest. Регрессий 0.
Closes audit ID C4 from docs/superpowers/specs/2026-05-15-portal-audit-design.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-review fixes для commit 4e77947 (C2 FilterChip popovers):
I-4 (latent interaction bug): Удалена двойная open-path в FilterChip
activator. v-menu сам управляет projectMenuOpen/managerMenuOpen через
activatorProps. Draft-state копируется при menu open → true через
watch(menuOpen, ...). Раньше:
- Activator click: menuOpen=true
- @click on FilterChip: onRedesignFilterClick → menuOpen=true (duplicate)
- Re-click для close: activator toggles false → onRedesignFilterClick
forces true back → menu не закрывается.
I-2 (inline toggle extract): Multi-line ternary @click заменён на
named methods toggleProjectDraft(proj) / toggleManagerDraft(name).
Консистентно с existing clearProjectDraft / clearManagerDraft. Также
unit-testable независимо от template.
onRedesignFilterClick остаётся для Status chip read-only behavior (P2
backlog Sprint 5). defineExpose обновлён: убран onRedesignFilterClick,
добавлены toggleProjectDraft/toggleManagerDraft/clearProjectDraft/
clearManagerDraft (для будущих spec'ов).
Vitest 3/3 C2-specs обновлены на прямой trigger projectMenuOpen=true
+ $nextTick (watcher seeds draft). Регрессий 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Job dispatch fell с SQLSTATE[42P01] "Undefined table: jobs" when
QUEUE_CONNECTION=database, потому что db/schema.sql не содержит таблиц
jobs/job_batches (CLAUDE.md §6 claim "3 default Laravel-миграции удалены"
не имел эквивалента для jobs в нашей schema; verified via
Schema::hasTable('jobs') = false).
Switch to redis — соответствует prod spec CLAUDE.md §2 "Кэш/очереди = Redis 7"
и существующему Memurai service (Redis 7-compat) per memory quirk #35
(PONG verified Task 4).
Verified end-to-end:
- php artisan config:clear
- config('queue.default') = redis
- Queue::connection('redis') instanceof Illuminate\Queue\RedisQueue
- SyncSupplierProjectsJob::dispatch(1) → Redis::llen('queues:default')
delta=1 (before=0, after=1, cleanup successful)
- Pest --parallel 742/739/3sk/0
- Vitest 758/3sk/0
Local app/.env (gitignored) уже на redis с прошлой сессии; этот commit
синхронизирует normative .env.example для new env setups.
Note: db/schema.sql миграция jobs/job_batches таблиц отложена (redis driver
= no DB queue tables needed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A11y rescan Pattern H — Vuetify <v-text-field> без `label` prop рендерит
empty `<label id="input-v-NN-label">` (referenced via aria-labelledby).
Pa11y/axe видит unlabelled input на /admin/billing (search «Поиск по
названию или ИНН») и /admin/system (search «Поиск по ключу или описанию»).
Initial naive fix добавил `aria-label="..."` — но ARIA priority говорит
aria-labelledby overrides aria-label, поэтому осталось violation.
Final fix: add `label="Поиск"` prop on VTextField. Vuetify рендерит
floating label с правильным accessible text → axe-core resolves через
aria-labelledby chain successfully. Placeholder сохранён (split: «Поиск»
теперь в label, «по названию или ИНН» / «по ключу или описанию» —
placeholder).
Files:
- AdminBillingView.vue:209-217
- AdminSystemView.vue:130-138
Closes Pa11y «label» violations на 2 admin URLs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A11y rescan Pattern A — Vuetify <v-app-bar-nav-icon class="d-md-none">
без accessible name. Pa11y/axe видит button в DOM даже на desktop где
он hidden via CSS — флагает «button-name» violation на 9 AppLayout views
(/dashboard, /deals, /kanban, /projects, /billing, /settings, /reports,
/reminders, /admin/tenants).
Fix: AppTopbar.vue:90-94 — `aria-label="Открыть меню навигации"`.
Closes 9 of 14 authenticated routes' a11y violations (down 14→5 affected
URLs after this commit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CsvReconcileJobTest used Bus::fake() (all jobs), silencing dispatch_sync of
RefreshSupplierSessionJob when a parallel afterEach wiped supplier:session.
Now: Bus::fake([RouteSupplierLeadJob::class]) + anonymous mock that re-puts
the session in handle(), making race-window recovery deterministic.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DatabaseTransactions did not prevent cross-session data accumulation in
liderra_testing; count assertions drifted (1465 managers, 519 projects).
RefreshDatabase runs migrate:fresh once per session (RefreshDatabaseState::migrated)
so stale data is wiped at start of each composer test run.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Audit #2 Phase 10.2 P2: axe-core 4.10 reported aria-tooltip-name
violation — <div role="tooltip"> had no accessible name. Adding
aria-label to <v-tooltip> passes it through to the rendered overlay.
Verified: axe-core on /admin/tenants — 0 tooltip violations post-fix.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Audit #2 Phase 14 P2: partition tables were not auto-created.
Without this entry the scheduler never called partitions:create-months,
causing partition exhaustion on the first day of each new month.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fake()->unique()->words(3,true) fixes quirk #77 deterministic collision
on projects(tenant_id,name) UNIQUE in --parallel runs.
test:parallel alias = pest --parallel --recreate-databases (quirk #62/#73).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Auto-generated by vite watcher during a11y-fix session 12.05.2026 evening:
— 1616: DashboardBalance.vue:32 div role=img (.runway-bar aria-prohibited-attr fix)
— 1617: KanbanView.vue:164 div role=region (scrollable-region-focusable fix)
— 1618: AdminLayout.vue:88 v-list role=navigation (aria-required-children fix)
Quirk 64 caveat: dev-indices обычно идёт в logical commit с UI-change.
Здесь catch-up от ранее закоммиченных fix'ов (Q.DEFER.002 sub-B batch),
отдельным atomic — приемлемо как cleanup audit-хвоста.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 6 audit found inconsistency in routes/web.php SPA-shell list.
Comment (line 188-190) declares «Регистрируем явно, а не catch-all»
for test isolation, but the explicit list missed:
- /reminders, /projects (main views from Plan 5)
- /admin and 7× /admin/* (added in Plans 4 + 5)
These paths worked via Route::fallback (line 211), but that risks
runtime-routes from Pest beforeEach('_test/*') being shadowed by
fallback BEFORE catch-all. Align explicit list with router/index.ts
to honor the documented rationale.
No behavioral change for production (same welcome view returned);
test-suite isolation contract restored.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>