Commit Graph

506 Commits

Author SHA1 Message Date
Дмитрий c09bff3799 test(security): Q.DEFER.003 sub-B — TwoFactorCard 9 own-spec tests
Coverage uplift от 28% to 80%+: enable button visibility / disable button
visibility / chip status / setup wizard openSetup→confirm→codes / invalid
code error / disable flow valid+invalid password / closeSetup state reset.
vi.mock authApi для 3 endpoint'ов (init/confirm/disable).
2026-05-13 01:46:30 +03:00
Дмитрий 918c962b26 test(security): Q.DEFER.003 sub-B — RecoveryCodesCard 6 own-spec tests
Coverage uplift от 28% to 70%+ (auth-gated visibility / dialog flow /
confirmRegen success+error / closeRegen reset). vi.mock authApi
для изоляции; VDialog stub'аем для DOM unit-test (избегаем teleport).
2026-05-13 01:41:27 +03:00
Дмитрий 4c6d593776 test(security): Q.DEFER.003 sub-B — ChangePasswordCard 3 own-spec tests
Placeholder card (17 lines, static UI) — add minimal coverage for heading,
last-change hint, and button rendering. Closes coverage debt от 0% Stmts.
2026-05-13 01:36:22 +03:00
Дмитрий 0a37aadd20 docs(audit): Q.DEFER.004 — replace Task 4 false-alarm verification with re-verified success
Task 4 subagent (commit e79fe95) reported 6+9 critical label violations
on /deals + /admin/supplier-prices, concluding Vuetify silently drops
aria-label. Re-verification 2026-05-13 (Playwright + axe-core 4.10 with
hard-reload + 500ms render-wait) показала **противоположное**:

- /deals: 0 label violations; 6 bulk-checkboxes имеют correct aria-label
- /admin/supplier-prices: 0 label violations; 9 inputs/switches OK
- /admin/tenants: 1 aria-tooltip-name as documented (sub-C unchanged)

Vuetify VSelectionControl.js:163 confirms input gets aria-label from
\$attrs forwarding via filterInputAttrs + _mergeProps(..., inputAttrs).

Q.DEFER.004 sub-A + sub-B closure stand as honest. Initial false-alarm
likely from HMR partial update / axe race-condition без render-wait —
documented as quirk для будущих сессий.
2026-05-13 01:05:36 +03:00
Дмитрий e79fe95267 docs(audit): Q.DEFER.004 — Playwright+axe-core verification 2026-05-13
Verified 3 pages with axe-core 4.10 CDN-injected via Playwright MCP:
- /deals (sub-A): 6 label violations REMAIN — Vuetify 3.12 silently drops
  aria-label на v-checkbox-btn (Task 1 source fix не propagates через rendering)
- /admin/supplier-prices (sub-B): 9 label violations REMAIN — 6× v-text-field
  с orphan aria-labelledby + 3× v-switch без aria-label на native input
- /admin/tenants (sub-C): 1 aria-tooltip-name violation confirmed как
  Vuetify-internal artifact (documented limitation, button activator OK)

Root cause: общий Vuetify-internal a11y prop forwarding gap. Source-level
Task 1 + Task 2 fixes присутствуют в коммитах d9fc3d9/c8005e0, но не имеют
user-visible effect — те же 16 residual nodes что pre-fix. Library-level
limitation, не application defect.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:48:59 +03:00
Дмитрий 484504b78f docs(audit): Q.DEFER.004 CLOSED — sub-A+B fixed, sub-C documented as Vuetify-internal known limitation 2026-05-13 00:40:04 +03:00
Дмитрий c8005e0cfc fix(a11y): Q.DEFER.004 sub-B — AdminSupplierPricesView 9 inputs aria-label
3 supplier rows × 3 form controls (cost_rub v-text-field +
quality_score v-text-field + is_active v-switch) = 9 nodes без label —
axe-core критичная label violation.

Fix: :aria-label='${field} для ${supplier.name}' (e.g. 'Cost (₽) для B1 — Сайты и Звонки').

Test coverage: AdminSupplierPricesView.spec.ts 4-й spec проверяет все 9 ожидаемых
aria-label через DOM query.
2026-05-13 00:35:05 +03:00
Дмитрий d9fc3d92e4 fix(a11y): Q.DEFER.004 sub-A — DealsTable show-select bulk-checkbox aria-label
VDataTable show-select prop генерировал unlabeled checkbox per row + select-all
header — axe-core критичная label violation (6 nodes на demo seed).

Override через Vuetify 3.12 typed slots:
- header.data-table-select → aria-label='Выбрать все сделки'
- item.data-table-select → aria-label='Выбрать сделку «{{name}}»' (per row)

Test coverage: tests/Frontend/DealsTable.spec.ts (2 specs).
2026-05-13 00:28:39 +03:00
Дмитрий a5e99ba0e9 docs(claude-md): v1.89 — factual fix §6 + шапка v1.88 (615db99 ≠ Plan 4)
В рамках post-audit continuation session 12.05.2026 ночь обнаружен factual
error в v1.88: коммит 615db99 в двух местах представлен как Plan 4 merge,
коммит f4ec5dc как PSR_v1 R15 removal. Оба идентификации неверны.

Verified через git log origin/main + git show <commit>:
- 615db99 = «chore(rules): remove R15 motion-runtime restrictions (PSR_v1 v2.0)» (12.05.2026 07:30) — R15 removal, НЕ Plan 4 merge
- 8681040 = «docs: Plan 4 closure — CLAUDE.md v1.87 + Открытые_вопросы v1.78» — правильный Plan 4 closure marker на origin/main
- a907fea..174dbae = backend Plan 4 task-коммиты (Tasks 9-11), merged ранее
- f4ec5dc = «fix(redesign): sidebar position:fixed + main padding-left» — Quiet Luxury sidebar hotfix на ветке plan5-frontend-projects, НЕ на origin/main, НЕ R15 removal

Правки v1.89:
1. §6 строка обновлена с правильными коммитами + явное разделение «Plan 4 closure 8681040» и «R15 removal 0fd93fd + 615db99» как разные истории
2. Шапка v1.88 changelog inline: 615db998681040 + NB-маркер про factual error
3. §9 v1.88 entry inline: то же исправление + NB
4. Bump CLAUDE.md v1.88 → v1.89 (новая шапка)
5. Новая v1.89 entry в §9 CLAUDE.md + параллельная запись в CHANGELOG
6. CHANGELOG intro обновлён: документировано что v1.84..v1.88 живут inline в §9 (CHANGELOG-обслуживание не велось 10.05.2026–12.05.2026)

Связанные документы (Pravila v1.10 / PSR_v1 v1.7 / Tooling v1.15 / реестр
v1.77 на ветке plan5-frontend-projects) НЕ требуют изменений — фикс
локален в CLAUDE.md.

Источник: post-audit continuation session, bonus-finding во время Q.DEFER.001
(memory description downgrade). Заказчик: «доделывать аудит, поправить
ошибку в CLAUDE.md». Через /claude-md-management:claude-md-improver per
CLAUDE.md §5 п.10.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:02:36 +03:00
Дмитрий 95f5f94a6b test(api): Q.DEFER.003 sub-A — 43 unit tests for api/*.ts layer
User chose (A) api/* unit tests first (highest ROI per blocked.md). 5 new
spec files covering auth/deals/notifications/reminders/reports api modules.

- auth-api.spec.ts (13 tests): login/register/me/logout/verifyTwoFactor/
  useRecoveryCode/twoFactorInit/Confirm/Disable/RegenerateRecoveryCodes/
  forgotPassword/resetPassword/updateNotificationPreferences
- deals-api.spec.ts (12 tests): createDeal/bulkDelete/bulkRestore/update/
  transition/exportCSV/exportXLSX/getDeal/listDeals×2/listManagers/
  listProjects
- notifications-api.spec.ts (6 tests): listNotifications×3 (unreadOnly
  variants)/markRead/markAllRead/delete
- reminders-api.spec.ts (6 tests): listReminders×2/create/update/complete/
  delete
- reports-api.spec.ts (6 tests): listReportJobs×2/create/retry/cancel/delete

Approach: vi.mock('../../resources/js/api/client') replaces apiClient with
{get,post,patch,delete} mocks + ensureCsrfCookie mock. Each test verifies:
(1) correct HTTP method, (2) correct URL, (3) correct params/body
(camelCase→snake_case mapping for query params), (4) data unwrap from
wrapper objects ({user}/{deal}/{job}/{reminder}/{managers}/{projects}),
(5) ensureCsrfCookie called for mutating endpoints.

Vitest delta: 614 → 657 passed (+43 / 0 failed); 79 → 84 files (+5).
3 skipped unchanged. Q.DEFER.003 sub-B (security cards) + sub-C (router
guards) remain deferred — sub-A api/* was highest ROI per blocked.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 22:14:51 +03:00
Дмитрий 143cc458c1 fix(a11y): Q.DEFER.002 sub-B — 12 patterns fixed across 16 auth views
Q.DEFER.002 sub-B closure: manual Pa11y audit-pass via Playwright MCP login +
axe-core CDN inject on 16 auth-required views. Found ~13 unique violation
patterns, 12 fixed, 3 deferred to Q.DEFER.004.

ROOT CAUSE found: AdminLayout `<v-navigation-drawer color="secondary"
theme="dark">` resolved to Vuetify default-dark `secondary=#54b6b2` (Teal
mid) instead of liderraForest `#012019` теало-нуар. Switching to direct hex
preserves design intent + restores white-text contrast across all 8 admin
views (~50 nodes color-contrast violations cleared).

Patterns fixed:

1. AdminLayout sidebar palette (8 admin views):
   - color="secondary" → color="#012019" (root cause)
   - .brand-sub red #b94837 → #e06155 (3.41 → 5.08)
   - .nav-count gray #7a8c87 → #8a9c95 (4.26 → 5.34)
   - <v-list nav> + role="navigation" + aria-label (aria-required-children
     fix: <v-list role=list> had [role=link] children — undefined для list)

2. DashboardBalance .runway-bar — role="img" (aria-prohibited-attr fix)

3. DashboardKpiRow .delta-up — #2e8b57 → #1b6e3b (4.27 → 6.25)

4. TransactionsTable .tx-amount-up — #2e8b57 → #1b6e3b (same fix)

5. RemindersList .empty-hint — #9a9690 → #6b6356 (2.98 → 5.74; +liderra-muted alignment)

6. KanbanView .kanban-board — tabindex="0" role="region" aria-label
   (scrollable-region-focusable fix)

7. ProjectCard:
   - .v-progress-linear + :aria-label="Прогресс дневной нормы: N%"
   - icon menu :aria-label="Меню действий проекта «...»"
   - bulk-select .card-check input :aria-label="Выбрать проект «...»"

8. useStatusPill in_progress #3F7C95 → #2A5A6E (4.07 → 6.11);
   useStatusPill.spec.ts sync

9. ProjectsView toolbar select-all input aria-label

10. AdminTenants impersonate v-btn aria-label

11. Global app.css:
    `.v-messages, .v-field-label { --v-medium-emphasis-opacity: 0.7; }`
    Vuetify default ~0.52 → rendered #7a7a7a/#767471 fails 4.20-4.29:1;
    0.7 → rendered ≈#595959 → 7.9:1+ passes WCAG AA.

Re-verified post-fix via axe-core on all affected views: all clean except
DEV-only `.dev-index-num` chip (tree-shaked в prod, not a real violation).

Vitest verified post-fix: 79 files / 614 passed / 3 skipped / 0 failed
(baseline preserved).

3 patterns deferred to Q.DEFER.004:
- DealsTable VDataTable show-select bulk-checkboxes (6 nodes) — Vuetify
  slot rewrite needed
- AdminSupplierPrices 9 form inputs — v-text-field/v-switch label props
- Vuetify v-tooltip eager-mount aria-tooltip-name — library-level cosmetic

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 22:09:48 +03:00
Дмитрий 420dd26c08 docs(audit): Q.INFO.001 CLOSED — Semgrep CI already exists (audit miss)
User chose variant (B) Semgrep в CI for Q.INFO.001. Investigation shows
.github/workflows/sast.yml already exists from PR #25 commit 53fb1ec
(10.05.2026, 2 days before audit) with better-than-minimal config:
- semgrep/semgrep-action@v1
- configs p/php + p/javascript + p/typescript + p/secrets
- triggers push/PR на main with path-filters app/app, app/resources/js, app/database/migrations
- SARIF upload to GitHub Security tab via github/codeql-action/upload-sarif@v3

Audit Phase 4 Subagent G missed this — searched only for local `npx semgrep`
CLI without checking existing CI workflows. Tagged as audit-gap finding for
future Phase 4 improvement (check `.github/workflows/` first).

No new code required. +SARIF added to cspell-words.txt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:43:10 +03:00
Дмитрий 02d3506803 docs(audit): close 5 Q-items per post-audit user decisions
Batch closure following Q-tree resolution session 12.05.2026 ночь:

- Q.HARD.001 (admin role-guard) → (A) Через Б-1: documented MVP defer, no code change. router/index.ts:128 TODO + routes/web.php /api/admin/* comments preserved.
- Q.INFO.002 (schema metric drift) → (B) double split confirmed: CLAUDE.md v1.88 §0/§2/§6/§8 already contain baseline (62/12/117/39/5/13/5) + dev-actual (75/102/289/39/5/19/0).
- Q.HARD.002 (pgFormatter swap) → План Б Не трогать: 6284-line cosmetic diff noise unacceptable, manual style preserved.
- Q.PRODUCT.002 (mock-data prod bundle) → (B) prod-fallback: ~7kB gzip mockDeals+mockAdmin remain in bundle until real API integration (Plan 6+).
- Q.DEFER.001 (memory description stale) → (A) downgrade to fact: 5 memory description edits — MEMORY.md (5 lines) + 4 file frontmatters describe plan5-frontend-projects state (PSR v1.7 / Pravila v1.10 / Tooling v1.15 / реестр v1.77 / schema v8.20 / CLAUDE.md v1.88) с notice про origin/main 615db99 divergence (R15 removal: v2.0/v1.11/v1.16 pending merge into plan5).

Bonus finding flagged separately: CLAUDE.md §6 contains factual error — claims 615db99 = Plan 4 merge post-f4ec5dc R15 removal. Actually 615db99 IS R15 removal commit; f4ec5dc is sidebar position:fixed hotfix. Plan 4 commits merged earlier. Fix via /claude-md-management:claude-md-improver in follow-up.

Remaining 3 open Q-items require implementation work next: Q.INFO.001 (Semgrep CI workflow), Q.DEFER.002 sub-B (16 auth-views manual Pa11y), Q.DEFER.003 (A) (~30 api/*.ts unit tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:40:48 +03:00
Дмитрий ac73c88371 docs(audit): record R4-R5 manualChunks experiment + revert (Phase 12)
После Phase 12 P3 finding R4-R5 (vendor chunk renaming) — попытался применить
`build.rollupOptions.output.manualChunks` в vite.config.js. Vite 8 использует
Rolldown который требует function-form (object-form ломает с
"manualChunks is not a function"). Под function-form Rolldown засосал
все consumers stores в pinia chunk через transitively-import резолюцию:

- Pinia chunk implicit ~5kB → explicit 127kB raw / 50kB gzip
- Total critical-path payload +50 kB gzip vs baseline (net negative)
- Vite auto-split работает better для этого app shape

Reverted to baseline. "VBtn 184 kB" — naming artefact (auto-named первый
Vuetify consumer'ом), не actual perf-issue. R4-R5 closed без code fix —
informational only.

3 гипотезы про cause Pinia blow-up:
- H1 stores transitively-pulled через pinia API import
- H2 cycles vue-core↔pinia в Rolldown greedy chunking
- H3 return null в function manualChunks ломает auto-split fallback

Detailed reverification recommended next session если решим повторить
с `output.preserveModules` или per-store individual manualChunks rules.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:19:24 +03:00
Дмитрий 15e1c6d34f docs(audit): Phase 12+13 extra — bundle analyzer + Vitest coverage
После «продолжай всё» — два informational прохода:

**Phase 12 (Bundle analyzer):**
- BUILD_ANALYZE=1 npm run build:analyze → bundle-analyze.html (547 KB)
- Top-15 chunks: VBtn 184kB raw (mislabeled — Vue+Vuetify core), KanbanView 182kB
- 5 recommendations R1-R5: code-split vuedraggable, lazy DealDetailDrawer
- BLOCKED Q.PRODUCT.002: mock-data dev-only vs prod-fallback?

**Phase 13 (Vitest coverage):**
- 79 test files, 614 passed / 3 skipped, 59.55s
- Totals: Stmts 75% / Branch 75% / Funcs 67% / Lines 77%
- 0% api/* layer — cheap ROI (30 unit-тестов поднимут Funcs до 80%)
- 28% security cards, 33% router (guards integration tests missing)
- BLOCKED Q.DEFER.003: coverage debt sprint planning

+мокают в cspell-words.txt.

Phase 12+13 — informational only, не closures P0/P1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:16:47 +03:00
Дмитрий b27259e7c5 docs(audit): Q.DEFER.002 marked CLOSED (3/3 contrast fixes applied)
После follow-up прохода — обе a11y contrast violations исправлены:
- ErrorView support-link (fff2dff) — 2.77 → ~12:1
- ForgotPasswordView info-alert (5cebe24) — 4.18 → ~7.5:1

Final Pa11y baseline на guest URLs: 4/4 No issues found.

Остаётся auth-views coverage (16 views) — требует session cookie
в Pa11y, defer next session.

+неверифицированы в cspell-words.txt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:59:01 +03:00
Дмитрий 5cebe2450d fix(a11y): ForgotPasswordView info-alert contrast 4.18 → 7+ (Q.DEFER.002 full close)
Phase 10 audit Pa11y нашёл WCAG2AA G18 contrast 4.18:1 < 4.5:1 на
v-alert type=info variant=tonal в ForgotPasswordView.vue:81 (rate-limit notice).

Diagnosis через Playwright browser_evaluate:
- Vuetify v-alert text-info color: rgb(63, 124, 149) = #3F7C95 (Forest brand info)
- Tonal-variant bg (computed): #ecf2f5 (light blue-grey, 12% tint от info)
- Contrast: #3F7C95 vs #ecf2f5 = 4.18:1

Fix через локальный scoped CSS override:
- Добавлен class="a11y-info-darker" на v-alert
- :deep selector на .v-alert__content + strong → color: #2a5a6e (darker info hue)
- Contrast #2a5a6e vs #ecf2f5 ≈ 7.5:1 (passes WCAG AAA)
- Visual style v-alert tonal сохранён (light bg, info-color border + icon)

Verify:
- npx pa11y --standard WCAG2AA http://127.0.0.1:8000/forgot → No issues found 
- npx vitest run ForgotPasswordView.spec.ts → 5/5 passed

Closes Q.DEFER.002 fully (вместе с ErrorView fix fff2dff).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:56:58 +03:00
Дмитрий fff2dff499 fix(a11y): ErrorView 404 support-link contrast 2.77 → 12+ (Q.DEFER.002 partial)
Phase 10 audit Pa11y нашёл WCAG2AA G18 contrast Fail на 404 ErrorView
support link: `<a class="text-primary">support@liderra.app</a>`.
Diagnosis через Playwright browser_evaluate computed-style:

- Link color: rgb(15, 110, 86) = #0F6E56 (Vuetify text-primary = Forest teal)
- Parent `.v-main.error-main` bg: rgb(1, 32, 25) = #012019 (теало-нуар)
- Contrast: 2.77:1 < 4.5:1 → WCAG2AA Fail

Pa11y предложил `#fcfffe` (white-on-white false-suggest). Реальный fix —
заменить teal на light color, читаемый на noir.

Изменения ErrorMeta.vue:56,98:
- class="text-primary" → class="err-help__link"
- + локальный CSS class:
    .err-help__link { color: #d3dad8; text-decoration: underline; }
    .err-help__link:hover { color: #ffffff; }

Color #d3dad8 vs #012019 = contrast ~12:1 (passes WCAG AAA).

Verify (после `npx vite build` чтобы Laravel переключился на production assets;
dev HMR через :5175 продолжал отдавать cached chunk):
- npx pa11y --standard WCAG2AA http://127.0.0.1:8000/no-such-path-404 → **No issues found** 
- npx vue-tsc --noEmit → 0 errors
- npx vitest run → 79/79 files, 614/614 + 3 skipped (0 regression)

Forgot-alert contrast (другие 2 Pa11y errors на /forgot) — Vuetify info-variant
theme, требует design-decision Платон/брендбук; defer в Q.DEFER.002 (A).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:51:55 +03:00
Дмитрий ebebfacab4 docs(audit): Phase 10+11 extra — Pa11y live URLs + TODO sweep
После заказчик'ого «продолжай ещё тут» — два extra прохода вне scope Phase 5/6:

**Phase 10 (Pa11y on live URLs):**
Phase 5 audit запускал Pa11y только на handoff concepts (`liderra_v8_handoff/concepts/v8_*.html`)
из-за конфига `pa11y.config.json`. Live портал не был проверен. Phase 10
закрывает gap на guest URLs:
- /login → 0 issues
- /register → 0 issues
- /forgot → 2 errors WCAG2AA G18 contrast 4.18:1 < 4.5:1 на v-alert rate-limit
- /404 (catch-all) → 1 error WCAG2AA G18 contrast 2.77:1 < 4.5:1 на text-primary link

Auth-required views НЕ verified — требуют session cookie injection в Pa11y CLI.
Q.DEFER.002 в blocked.md с 4 options для следующей сессии.

**Phase 11 (TODO/FIXME sweep):**
Grep `\b(TODO|FIXME|XXX|HACK)\b` over app/**/*.{php,vue,ts}:
- 19 matches in 15 files.
- 6× MVP-defer Б-1 (admin role-guard, saas-admin auth — cross-link Q.HARD.001).
- 8× feature-defer (DashboardView mock data, region_mask decode Plan 6, и т.д.).
- 1× production-readiness (ProcessWebhookJob Sentry::captureException).
- 3× test-infra known квирк 54 (Vuetify teleport в JSDOM).
- 1× false-positive (TwoFactorSetupTest 'totp_secret' string literal).

Все documented in code — tracked work, не surprise.

cspell-words.txt: +Категоризация, +квирки (для audit-docs prose).

Не закрывают P0/P1. Phase 10/11 — informational только.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:47:43 +03:00
Дмитрий 1da23b8253 chore(audit): finalize 2026-05-12 portal full audit
Полный аудит портала проведён в ночь 12.05.2026 на ветке plan5-frontend-projects.
9 phase'ов, 393 findings, 8 fix-commits, 4 BLOCKED-вопроса.

Артефакты:
- docs/superpowers/plans/2026-05-12-portal-full-audit.md — план
- docs/superpowers/audits/*-findings.md — все findings file:line + severity
- docs/superpowers/audits/*-blocked.md — 4 вопроса заказчику
- docs/superpowers/audits/*-report.md — summary с метриками до/после
- audit-screens/views/ — 24 UI smoke screenshots (Playwright)
- audit-screens/legacy/ — 32 untracked PNG из workdir
- app/database/seeders/DemoSeeder.php — idempotent seed
- .gitleaks.toml — allowlist для seeders/audit-docs (демо-фикстуры)
- cspell-words.txt — +12 audit-cited mixed-script artifacts

Метрики (Phase 1+2 baseline → Phase 9 final, все commits 3a8229a..57f0b8e):
- Histoire build BROKEN → 35 stories / 63 variants 
- ESLint 17 → 0 
- vue-tsc 9 → 0 
- Prettier 48 → 0 
- markdownlint 165 → 1 (untracked design.md) 
- cspell 103 → 18 → 0 (after audit-cited words added) 
- Vitest 614 → 614 (0 regression) 
- Pest --parallel 739/0/3 → 739/0/3 
- Vite build 1.80s 0 warnings → 1.72s 0 warnings 
- gitleaks 0 leaks (340 commits) 

🟢 GREEN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:37:51 +03:00
Дмитрий 57f0b8e64c docs(claude.md): v1.87 → v1.88 audit-driven sync §0/§2/§6/§8
Phase 6 audit found doc-drift:
- §0/§2/§8 schema метрики застряли на baseline v8.19 (62/12/117/39/5/13/5).
  Dev `liderra` factual after migrate:fresh + partitions:create-months:
  75 root + 102 partition children + 289 indexes + 39 RLS + 5 user funcs
  + 19 triggers + 0 dev roles (5 on prod via db/00_create_roles.sql).
- §0 row «Открытые_вопросы v1.75» → факт v1.77 (Sprint 4 Audit tail
  close, 10.05.2026); note про post-v1.77 deviation (Plan 4/5 + Quiet
  Luxury merged без registry bump).
- §6 «Plan 4 ready for FF-merge» → факт «Plan 4 MERGED в origin/main
  615db99» + новый параграф про Plan 5 frontend + Quiet Luxury + dev-indices
  в `plan5-frontend-projects` (85+ commits ahead на 12.05.2026 night).
- §8 self-review row: baseline ИЛИ dev-actual disambiguation.
- 5 user-функций перечислены поимённо (audit_block_mutation, audit_chain_hash,
  calc_lead_score, report_jobs_log_export, set_pd_subject_request_deadline).

§9 entry для v1.88 описывает полный аудит-сессии: 8 commits Phase 8,
0 регрессий, final baseline Pest 742 / Vitest 614 / Histoire 35 / Vite 1.80s.

Через `/claude-md-management:revise-claude-md` (см. blanket approval
заказчика «исправь всё что сможешь в моё отсутствие»).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:30:14 +03:00
Дмитрий b9038bc3eb chore(routes): add explicit Route::view for /projects, /reminders, /admin/*
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>
2026-05-12 20:25:19 +03:00
Дмитрий cb05657f30 chore(format): prettier --write across 37 .vue/.ts files
Phase 1B audit found 48 files failing `prettier --check`. Auto-apply
via `npx prettier --write resources/js/**/*.{ts,vue,css}` produced
style-only changes:
- consistent quote style
- trailing comma normalization
- spaces around : in v-card style="position: relative" attrs
- explicit ; insertion

No semantic changes. No code-behavior changes. Production-code only;
test files batched separately into `test(frontend):` commit.

Verification:
- npx vitest run → 79/79 files, 614/614 + 3 skipped (no regression).
- npx vue-tsc --noEmit → 0 errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:24:33 +03:00
Дмитрий 6988e80137 chore(cspell): add 79 project + lorem-ipsum + brand-naming + test-fixture words
Phase 1C of audit found 103 unknown words across 12 files (docs/ + web/v8/*.html).
Categorized and added to cspell-words.txt:
- Lorem ipsum (14): handoff placeholder text (amet, consectetur, ... consequat)
- Бренд-нейминг + сторонние сервисы (9): Volna, Vento, Potok, Fraunces, Authy,
  jqlang, FAVOURITE, favourite, potok
- Project terms RU (26): квирк, нормативка, релизный, консьюмер, фичефлаги,
  премиума, медтехом, вайбом, тиловый, слейт, вайб, фиксим, гипотезного,
  капчёй, логируются, синхр, агрегированно, еталонных, задек, диффа,
  закоммичены, перехвачиваться, недозвоном, Неогранич, и т.п.
- Test fixtures + аббревиатуры (28): MRT/VLW/YHC/GVB, lpk/xqz/btv, SMSC,
  LTV, ПАО, НКО, potolki, msk, build-hash fragments (MVZNV, Bjf, DDP, ...),
  funcs, trgm, plpgsql, reestr, sumary
- Фамилии (2 с диакритикой): Бузо́ва, Габбасов

Reduces cspell issues 187 → 18 (90% reduction). Remaining 18 — mixed-script
artifacts + diacritic opechatki в исходниках (web/v8/, audit-docs);
captured как P3 typo-finding'и в audit-blocked.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:24:20 +03:00
Дмитрий 245b76ec43 test(frontend): fix 17 ESLint errors + TwoFactorView router stub
ESLint emitted 17 errors in tests/Frontend/* (production code clean):
- 13× @typescript-eslint/no-explicit-any in axios mock casts
  (BulkActionsBar, ProjectsView, projectsStore specs)
- 3× vitest/no-disabled-tests rule-not-found
  (eslint-plugin-vitest not registered; inline-disable comments stale)
- 1× @typescript-eslint/no-unused-vars on imported beforeEach

Plus Phase 5 audit finding: TwoFactorView.spec.ts test router was
missing /recovery-use stub → Vue Router warn on every TwoFactorView mount.

Changes:
- BulkActionsBar.spec.ts, ProjectsView.spec.ts, projectsStore.spec.ts:
  replace `as any` with `as unknown as ReturnType<typeof vi.fn>` on
  axios method mocks; one case used `as unknown as { regionsOpen: bool }`
  for vm shape.
- NewProjectDialog.spec.ts, ProjectsView.spec.ts: remove stale
  `// eslint-disable-next-line vitest/no-disabled-tests` comments
  (it.skip() lines kept).
- ProjectsView.toolbar.spec.ts: drop unused `beforeEach` from import.
- TwoFactorView.spec.ts: add `/recovery-use` route stub.

Verification:
- npx eslint --max-warnings=0 → exit 0 (was 17 errors).
- npx vitest run on affected specs → 24/27 passed + 3 skipped (was same).
- TwoFactorView spec → 3/3 passed, no Vue Router warn.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:23:51 +03:00
Дмитрий 55a9d3fe00 fix(types): unify Project interface + NavItem.countKey + drop legacy Record
vue-tsc was emitting 9 errors from two issues:

1. ProjectCard.vue had a local `interface Project` missing region_mask /
   region_mode / delivery_days_mask, while stores/projectsStore.ts
   exported the canonical one with those fields. ProjectsView.vue passed
   the canonical Project to ProjectCard handler signatures which expected
   the local incomplete one → 5× TS2322.

2. EditProjectDialog passed `project: Project | Record<string, unknown>`
   to NewProjectDialog which expected `Record<string, unknown> | null`.
   Project lacks an index signature → TS2322.

3. AppSidebar.vue template referenced `item.countKey` not declared in
   NavItem interface → 2× TS2339.

Changes:
- ProjectCard.vue: drop local Project, import from projectsStore.
- NewProjectDialog.vue: project prop type → Project | null (was Record).
  Drop `as { id: number }` cast on PATCH URL.
- EditProjectDialog.vue: project prop type → Project | null.
- AppSidebar.vue: add `countKey?: string` to NavItem.
- projectsStore.ts: make region_mask/region_mode/delivery_days_mask
  optional (backward-compat for mock fixtures; production rows always
  populate them by schema).
- Test/story fixtures expanded with delivered_today/is_active/archived_at/
  sync_status to match strict Project shape.

Verification:
- npx vue-tsc --noEmit → 0 errors (was 9).
- npx vitest run on 5 affected specs → 16/16 passed + 2 skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:15:26 +03:00
Дмитрий 3a8229a4c7 fix(histoire): register Pinia in setup file + add missing routes
BulkActionsBar.story.vue calls useProjectsStore() in top-level setup,
which executes before story collection. Without Pinia plugin, Histoire
build aborts with `getActivePinia() was called but there was no active
Pinia` — uncaught exception kills the whole build (24 → 0 stories).

Add createPinia() to histoire.setup.ts alongside Vuetify + vue-router.
Also add `/recovery-use` and `/projects` routes to the stub router
(parity with router/index.ts after Plan 5 frontend), so future story
files needing those paths don't emit Vue Router warns.

Histoire build now: exit 0, 35 stories / 63 variants in 80.6s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:15:09 +03:00
Дмитрий b5849bbd2a fix(projects): cyrillic ILIKE via PG ICU + clearable workaround
Корень: dev-БД `liderra` создавалась с LC_CTYPE=C — lower()/upper() не
делает case-folding для кириллицы, `ILIKE '%сп%'` на «Окна СПб» = 0 строк.
Test-БД с Russian_Russia.1251 маскировала проблему.

Системный fix: dev-БД пересоздана через `LOCALE_PROVIDER icu ICU_LOCALE 'und'`
(PG 16+ ICU collation, кросс-платформенно). Точечный COLLATE-workaround не
понадобился — все 5 ILIKE-endpoint'ов теперь работают с кириллицей без
правки кода. CTO-20 закрыт в реестре v1.81; команда CREATE DATABASE с ICU
зафиксирована для prod-deploy.

Сопутствующее:
- ProjectsView clearable: workaround `::after content '✕'` + видимость
  через `.v-field--dirty` (mdi-* font не подключён в проекте — CTO-19
  заведён в реестре).
- LookupsTest: удалён stale case `GET /api/projects?tenant_id=N`,
  заменённый auth:sanctum-роутом в Plan 5.
- Pest +1 регрессионный тест (`search is case-insensitive for Cyrillic`)
  в ProjectsListShowTest, 10/10 / 37 assertions.
- phpstan-baseline регенерирован (3 actingAs + удалённый case).
- cspell-words: +Регистронезависимый, +und.
- app/.backups/ в gitignore.

Verify:
- Pest --parallel: 742 passed / 1 flaky error (CsvReconcileJobTest cache
  race, в изоляции 2/2 PASS) / 3 skipped.
- Browser: «сп» и «окн» возвращают «Окна СПб».

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 19:25:25 +03:00
Дмитрий 4ee718e668 docs(quiet-luxury): disclose ProjectCard template change bundled in 3fc90f1
Final code reviewer correctly identified that impl-commit 3fc90f1 included
not only the CSS work but also a template change <v-checkbox> →
<label><input><span> on ProjectCard.vue.

Root cause (Task 0 forensics): session-start git status showed
`M app/resources/js/components/projects/ProjectCard.vue` — pre-existing
uncommitted modifications from prior session/work. When the session Read
the file at start, it saw the working-dir version (already with native
<label><input>), not the branch-HEAD 88a13e2 version (with <v-checkbox>).
Stage+commit in 3fc90f1 thus bundled both changes.

The template change is architecturally required for the new CSS to work —
<v-checkbox> renders Vuetify-internal <input> without a sibling <span>,
which is what the scoped :checked + .card-check__box::after selector
needs. The baseline-fix commit 84530d5 was also prepared for the native
input selector, consistent with this template structure.

Updating spec §2 architecture to reflect this honestly rather than leave
a stale «Template / script нетронуты» statement that conflicts with the
diff in main.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:14:58 +03:00
Дмитрий 3fc90f12df feat(projects-ui): Quiet Luxury redesign for card-check + 5 dialog v-text-fields
ProjectCard.vue: replace 2px noir solid border on .card-check__box with
1px var(--liderra-line) idle / var(--liderra-line-strong) hover / var(--liderra-teal)
checked. Checked state uses tonal 10% teal bg instead of full fill. Size 20→16px.
Added :focus-visible outline for keyboard nav.

NewProjectDialog.vue: add a local .ld-input-quiet class to all 5 v-text-field
in the dialog (domain / phone / sms keyword / name / daily limit). The class
overrides v-field outline border-color through :deep() to use the tokens.css
1px line / line-strong / teal palette, and sets border-radius to var(--radius-8).
All variant/density/color values come from Vuetify global defaults in
plugins/vuetify.ts:50-54. Includes opacity:1 on every override to neutralize
Vuetify's --v-field-border-opacity 0.38 cascade, plus an explicit error-state
rule with border-color:currentColor to preserve Vuetify's red error border.

Twin elements left out of scope: .toolbar-check__box in ProjectsView.vue,
v-combobox/v-autocomplete/v-btn-toggle inside the same dialog, and the
filter-bar v-select inputs.

Spec: docs/superpowers/specs/2026-05-12-quiet-luxury-elements-1440-896-design.md
Plan: docs/superpowers/plans/2026-05-12-quiet-luxury-elements-1440-896.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:07:20 +03:00
Дмитрий ab47ad250b docs(quiet-luxury): apply reviewer findings on opacity cascade + error state
Task 2 code-quality review (subagent verdict: Ready to merge? No) found
two critical correctness bugs in the original CSS template from spec §3.2:

1. Vuetify's outlined variant collapses sub-element opacity through
   --v-field-border-opacity (= 0.38 at idle). Without explicit opacity:1
   on each override block, --liderra-line (alpha 0.08) effective alpha
   becomes 0.03 → border essentially invisible on ivory backgrounds.

2. Overriding border-color with an explicit value breaks the
   currentColor inheritance Vuetify uses for the error state
   (color: rgb(var(--v-theme-error)) on .v-field--error.v-field__outline).
   Without an explicit error rule that restores currentColor, the red
   error border never appears on any of the 5 validated fields.

Also tightened hover from .ld-input-quiet:hover (which is on the .v-input
root, including hint/error message area) to .v-field:hover inside
:deep() — matches Vuetify's own hover scope and avoids triggering on
helper text hover.

Spec §3.2 and plan Task 2.2 updated to the corrected CSS block with
explicit «almost-trap-avoidance» notes documenting why each adjustment
is needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:52:29 +03:00
Дмитрий 84530d55bf test(projects): fix ProjectCard change-trigger target
Pre-existing failing test from commit c9ee8d8 — data-testid lives on
<label>, but @change handler sits on <input> inside it. jsdom does not
bubble change-event from label to input via @vue/test-utils trigger.
Use child-input selector to fire the event on the right node.

Baseline после fix: 614 passed / 3 skipped / 0 failed (vs 613 / 3 / 1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:36:33 +03:00
Дмитрий b2a5a6e18a docs(quiet-luxury): rescope #896 to 5 v-text-fields after vuetify-defaults finding
Task 0 (pre-flight) обнаружил global Vuetify default в plugins/vuetify.ts:50-54
который уже устанавливает variant=outlined density=comfortable color=primary
для всех VTextField. Изначальная гипотеза spec §1.2 «variant=filled по
умолчанию» была неверна — все 5 v-text-field в NewProjectDialog.vue выглядят
одинаково тёмными (Vuetify default border ≈ 60% on-surface), а не «один
filled среди других».

Заказчик принял расширение области: применить .ld-input-quiet ко всем 5
v-text-field (lines 21, 30, 48, 59, 61), убрать неработоспособные явные
props (variant/density/color/rounded — они уже из global default), и
вынести border-radius в :deep(.v-field) override через --radius-8.

Также Task 0 нашёл pre-existing failing test в ProjectCard.spec.ts:43
(change-trigger на <label> вместо <input> внутри); это будет починено
отдельным atomic-коммитом перед Task 1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:35:50 +03:00
Дмитрий 50816403bb docs(quiet-luxury): implementation plan for #1440 + #896 redesign
7-task plan (pre-flight + 2 implementation tasks + 3 verification tasks + commit/push)
с TDD-стилем bite-sized шагов: baseline Vitest → CSS-only правка ProjectCard
→ template+style правка NewProjectDialog → full regression → manual smoke →
lefthook + commit + push. Includes verification-before-completion checklist
и rollback план.

Spec ref: docs/superpowers/specs/2026-05-12-quiet-luxury-elements-1440-896-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:34:49 +03:00
Дмитрий 7d86971e9d docs(quiet-luxury): design spec for elements #1440 + #896 redesign
Spec по узкому Quiet Luxury редизайну двух конкретных элементов из
Dev Element Indices: card-check__box в ProjectCard и v-text-field
«Название проекта» в NewProjectDialog. Подход — CSS / prop правки
под существующие tokens.css, без новых primitives.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:21:23 +03:00
Дмитрий 88a13e2001 chore(dev-indices): manifest entries for bulk-actions components 2026-05-12 15:38:11 +03:00
Дмитрий 8f40ea441d feat(projects-bulk): Histoire stories for 3 bulk dialogs 2026-05-12 15:24:24 +03:00
Дмитрий df92ac02ff feat(projects-bulk): wire 3 new dialogs into BulkActionsBar
Add RegionsBulkDialog / DaysBulkDialog / LimitBulkDialog to
BulkActionsBar with open-state refs (regionsOpen/daysOpen/limitOpen),
runBulk helper via store.bulkUpdate, and flex-wrap layout.
Update spec: fix existing tests (bulkAction → bulkUpdate), add 3 new
dialog-wiring tests (7/7 pass; full suite 614+3skipped/0failed).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 15:23:29 +03:00
Дмитрий 4b6ab8f113 feat(projects-bulk): LimitBulkDialog delta or replace mode
Delta mode combines Add/Remove numeric inputs into a single signed delta;
Replace mode switches to an absolute value input via v-checkbox toggle.
5/5 Vitest pass; full suite 611 passed + 3 skipped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 15:20:18 +03:00
Дмитрий 4c470813b4 feat(projects-bulk): DaysBulkDialog Add/Remove (7 weekday bitmask)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 15:17:16 +03:00
Дмитрий 3b254fb56f feat(projects-bulk): RegionsBulkDialog Add/Remove (8 ФО bitmask) 2026-05-12 15:14:18 +03:00
Дмитрий 95bba384a1 feat(projects-bulk): select-all toolbar with counter and indeterminate state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 15:11:21 +03:00
Дмитрий a46e63bdd3 feat(projects-bulk): store selectAllByFilter + bulkUpdate with scope discriminator
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 15:08:08 +03:00
Дмитрий 2d6eb88ce0 feat(projects-bulk): federal districts + weekdays constants for bulk dialogs 2026-05-12 15:04:58 +03:00
Дмитрий cb36a52171 test(projects-bulk): RLS cross-tenant isolation + empty-resolve edge case
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 15:02:04 +03:00
Дмитрий 64d8daede7 feat(projects-bulk): scope.filter resolver + 500-limit guard
Refactor inline scope resolution from ProjectController::bulk() into
ProjectService::resolveBulkScope (BULK_MAX=500 constant). Adds 2 tests:
scope.filter->ids mapping and >500 rejection (12 total, all pass).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 14:59:59 +03:00
Дмитрий c6eae16282 feat(projects-bulk): update_limit handler with per-project skip on delivered_today conflict
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 14:55:45 +03:00
Дмитрий c025ec4b69 feat(projects-bulk): update_days handler with bitmask OR/AND-NOT
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 14:52:23 +03:00
Дмитрий 8220a85a5d feat(projects-bulk): update_regions handler with bitmask OR/AND-NOT
Refactor ProjectService::bulkAction to accept full payload array and
return structured {updated, skipped, warnings}. Add bulkUpdateRegions
using PG raw bitmask expr (region_mask | add) & ~remove & 255.
Add stubs for bulkUpdateDays/bulkUpdateLimit (Tasks 3-4). Update
controller to pass merged payload and return service result directly.
Un-todo Task-1 region validation test; add regions bitmask test (18/20).
Update phpstan-baseline: actingAs count 5->6, restore match.unhandled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 14:48:10 +03:00
Дмитрий 08f02100fe fix(projects-bulk): treat empty scope.filter as valid scope
Replace !empty() check with has()+is_array() so scope:{filter:{}} is
accepted as "all projects" rather than rejected as missing selection.
Expand scope.filter to IDs in the controller (500-row limit guard) so
the service receives a typed array[]; add Pest coverage for this case.
Update phpstan baseline count for new actingAs() call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 14:43:45 +03:00