Дмитрий
f9820460fa
feat(pdd): regions multi-select autocomplete + bitmask binding
...
Реализует Out-of-plan «Region multi-select autocomplete» из parent PDD spec.
Spec: 4f60add . Plan: 159ed3e .
Component (ProjectDetailsDrawer.vue):
- import REGIONS из constants/regions
- selectedRegions: Ref<number[]> + selectableRegions (filter code !== 0
для исключения «Вся РФ» sentinel — fixes latent NewProjectDialog bug)
- maskToCodes(mask): reverse-decompose bits 1..31
- reseedFromProject: +selectedRegions.value = maskToCodes(form.region_mask)
- watch(selectedRegions): forward-encode mask + mode (include при empty, exclude иначе)
- Template: v-autocomplete multi+chips+clearable между Лимитом и Днями
Tests (ProjectDetailsDrawer.spec.ts): 17 passed (14 prior + 3 new):
- renders region chips when project has non-zero region_mask
- selecting regions encodes mask + sets mode=exclude on save
- clearing all regions resets mask=0 + mode=include on save
NB: config.global.plugins = [createVuetify()] добавлен в spec.ts — v-autocomplete
требует Vuetify defaults provide context. Все 17 PDD tests + 8/1sk ProjectsView
integration green (0 regressions).
Backend без изменений (region_mask + region_mode payload уже в Task 5 onSave).
2026-05-14 17:51:56 +03:00
Дмитрий
ae6a370b06
feat(pdd): Delete button with confirm + archive + close
2026-05-14 14:54:55 +03:00
Дмитрий
8aca5b1ba9
feat(pdd): Pause/Resume button with toggleActive + dynamic label
2026-05-14 14:48:24 +03:00
Дмитрий
86b18fc396
feat(pdd): Save action — PATCH /api/projects/{id} + 422 errors
2026-05-14 14:41:28 +03:00
Дмитрий
f47ace40f4
feat(pdd): reseed form on project.id change
2026-05-14 14:35:54 +03:00
Дмитрий
66d0d48adf
feat(pdd): emit close on X/Cancel/ESC
2026-05-14 14:28:49 +03:00
Дмитрий
fa01951d27
feat(pdd): render project name/limit/days form fields
2026-05-14 14:21:07 +03:00
Дмитрий
7d77187eb3
test(pdd): scaffold ProjectDetailsDrawer + null-project no-open test
2026-05-14 14:13:52 +03:00
Дмитрий
6387706be6
fix(a11y): .sep dot separator contrast 2.92:1 → 5.33:1 (Pattern B)
...
A11y rescan Pattern B — scoped CSS `.sep { color: #92907b; }` повторяется
в 8 компонентах (page-stats / page-meta / hero-meta containers с точкой-
разделителем `·`). На ivory page background #f6f3ec даёт contrast
2.92:1, ниже WCAG 2.1 AA 4.5:1 threshold.
Fix: #92907b → #6b6356 — same warm-grey hue, darker tone, gives
5.33:1 contrast. 8 files:
- views/{DealsView,BillingView,KanbanView,ReportsView}.vue
- components/dashboard/DashboardPageHead.vue
- components/deals/DealDetailHero.vue
- components/admin/tenants/TenantsStatsHeader.vue
- components/admin/tenant-detail/TenantDetailHeader.vue
Closes Pa11y «color-contrast» violations на /dashboard /billing /reports
(8 .sep elements total flagged).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-14 10:07:11 +03:00
Дмитрий
667befde96
fix(a11y): add aria-label to mobile nav-icon button (closes Pattern A)
...
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 >
2026-05-14 10:06:52 +03:00
Дмитрий
c5242271d7
chore(p3): close P3 tooling and structural mini-fixes
...
Closes Audit #3 P3 batch.
Changes:
1. **knip.config.ts cleanup** — remove 4 stale config hints flagged in
Audit #3 Phase 1B (`ignore: tests/**` redundant since `project` is
`resources/js/**`; `ignoreDependencies` for vitest/@vue/test-utils/jsdom
redundant since knip auto-detects test frameworks). Add `histoire.config.ts`
+ `resources/js/histoire.setup.ts` to entry — closes 2 documented FPs
(histoire.setup.ts + @histoire/plugin-vue unused-flag). Verified:
`npx knip` exits 0 clean.
2. **Admin table actions column header label** — change `title: ''` →
`title: 'Действия'` in:
- TenantsTable.vue (actions column, /admin/tenants)
- AdminSupplierPricesView.vue (actions column, /admin/supplier-prices)
Closes axe-core `empty-table-header` violation seen in Audit #3 Phase 7
on /admin/tenants. Header is now visible in UI (better UX than sr-only
sleight-of-hand).
3. **npm overrides for lodash** in `package.json` — pin `pa11y-ci > lodash`
to ^4.17.21. Verified: `npm ls lodash` resolves to lodash@4.17.23 (latest
4.x; CVE-2021-23337 + GHSA-f23m patched in <4.17.21, our version is above
that). npm audit may still surface advisory ranges as informational.
4. **Decision doc for pgFormatter (Q.HARD.002)** — explicit FIX-DEFER with
3-hypothesis comparison (Strawberry Perl install vs sqlfluff replacement
vs Docker pg_format vs drop SQL formatting). Decision: drop automated
SQL formatting until Б-1 closure; squawk (linter) covers correctness.
Addendum: axe-core .v-overlay-container region landmark — no permanent
axe-core test setup exists, so no whitelist needed at this point.
Verification:
- knip: 0 issues
- vue-tsc: 0 errors
- ESLint: 0 errors
- Vitest: 91 files / 736 passed / 3 skipped (no regressions)
- Vite build: 2.03s
Plan: docs/superpowers/plans/2026-05-14-audit3-deferred-fixes.md Task 4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-14 08:38:51 +03:00
Дмитрий
bf84568837
fix(a11y): add aria-label to VTooltip on /admin/tenants impersonate btn
...
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 >
2026-05-13 13:38:21 +03:00
Дмитрий
e280edd431
style(frontend): apply prettier --write — fix formatting drift
...
4 files reformatted (import list expansion, line-length wrapping).
Vitest 88/683+3sk green.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-05-13 13:30:51 +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
Дмитрий
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
Дмитрий
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
Дмитрий
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
Дмитрий
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
Дмитрий
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
Дмитрий
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
Дмитрий
5c8ad2738a
feat(layout): dark topbar + sidebar cleanup + DevIndexBadge moved below
...
Sidebar: убраны Менеджеры/Напоминания; Работа в порядке
Проекты/Сделки/Канбан/Дашборд; Команда — только Настройки;
снят useRemindersStore (был только под reminders badge).
Topbar: тёмный фон linear-gradient(noir → #04261E) совпадающий
с sidebar #1271 ; убран breadcrumb «Рабочая область»;
v-toolbar__content padding-left:240 (не уходит под sidebar).
DevIndexBadge: top:64 (ниже топбара, не перекрывает user-chip).
Vitest AppLayout 15/15 PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-12 14:32:03 +03:00
Дмитрий
9a7615b257
fix(dev-indices): Esc pause-hover + skip inert Vue compiler tags
...
#1 (review-Important) — Esc now also calls pauseHover(2000) so the next
mousemove doesn't re-target the cursor element within 16ms. User gets
2 seconds to move off before hover re-engages.
#4 (review-Important) — Plugin walker now skips data-dx injection for
inert Vue compiler tags (template / slot / component / Transition /
TransitionGroup / Suspense / KeepAlive) but still recurses into their
children with the tag preserved in ancestor chain (keeps descendant
signatures stable). Manifest regenerated — no more phantom IDs that
reference no-DOM-element nodes.
Other review findings (CI integration, save-amplification, code-style
polish) skipped: this feature is temporary, will be removed at final
release.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-12 12:32:15 +03:00
Дмитрий
d238ca5f4a
feat(dev-indices): overlay Alt-keys (up/down) + Alt+Shift+I toggle + mini-badges
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-05-12 12:05:53 +03:00
Дмитрий
d8c33b4cd6
feat(dev-indices): DevIndexOverlay (hover badge + click-copy + Esc + AppShell mount)
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-05-12 12:02:38 +03:00
Дмитрий
f4ec5dcafa
fix(redesign): sidebar position:fixed + main padding-left — restore main content visibility
...
Hotfix: после Task 12 замены v-navigation-drawer на plain <aside> sidebar остался position:static и толкал v-main в flow ниже (y=901), весь контент уезжал за viewport. Добавлен .ld-sidebar position:fixed top:0 left:0 height:100vh z-index:1006 + .app-main padding-left:232px. Verified via Playwright snapshot — Dashboard KPI/charts отрисованы корректно.
2026-05-12 10:48:15 +03:00
Дмитрий
e2669270f3
feat(redesign): Task 17 — ProjectCard tokens (hover lift + JetBrains Mono numerics)
2026-05-12 10:22:36 +03:00
Дмитрий
22e6bdf8b8
feat(redesign): Task 16 — KanbanView StatusPill + hover lift (motion #4 )
...
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-12 10:19:21 +03:00
Дмитрий
2f46a3e5ec
feat(redesign): Task 15 — DealsView filterbar + density + StatusPill + hover lift (motion #2,#4)
2026-05-12 10:13:19 +03:00
Дмитрий
35662f7b56
feat(redesign): Task 14 — DashboardView KPI count-up (motion #1 ) + live pulse
2026-05-12 10:06:08 +03:00
Дмитрий
3f956224bd
feat(redesign): Task 12 — AppSidebar двухтоновый shell + ⌘K stub + active marker (motion #7 )
2026-05-12 09:54:54 +03:00
Дмитрий
2707ff64ab
feat(redesign): Task 11 — DensityToggle component (compact/comfortable + persist)
2026-05-12 09:49:37 +03:00
Дмитрий
0b2ec5b802
feat(redesign): Task 10 — FilterChip component (label + count + active states)
2026-05-12 09:47:02 +03:00
Дмитрий
52cc64c9e6
feat(redesign): Task 9 — Kbd component (⌘K, Esc badges; light+dark variants)
2026-05-12 09:45:22 +03:00
Дмитрий
ff3bc8bcc1
feat(redesign): Task 8 — StatusPill component + 14-variant Histoire story
2026-05-12 09:43:02 +03:00
Дмитрий
0245f12b51
chore(dev): inject DevIndexBadge for visual feature feedback on localhost
2026-05-12 04:45:18 +03:00
Дмитрий
1c3989a6df
feat(frontend): Plan 5 Task 10 — EditProjectDialog wrapper + BulkActionsBar + 7 tests
2026-05-11 19:41:53 +03:00
Дмитрий
92082606e3
feat(frontend): Plan 5 Task 8 — ProjectsView + projectsStore (no polling) + 9 tests
2026-05-11 19:38:59 +03:00
Дмитрий
c9ee8d866e
feat(frontend): Plan 5 Task 7 — router + nav + regions + ProjectCard + story
2026-05-11 19:31:23 +03:00
Дмитрий
79ff60ffd9
refactor(frontend): Sprint 4 Phase B/3 — split 2 utility views (audit O-refactor-04 хвост)
...
ErrorView 320→178 (+ ErrorBrand 54 + ErrorIllustration 31 + ErrorActions 55 + ErrorMeta 102).
DashboardView 302→84 (+ DashboardPageHead 65 + DashboardKpiRow 97 + DashboardBalance 124).
State (config, errorCode в ErrorView; range/kpis/balance в DashboardView) остаётся
в parent ради единого route.meta-driven flow + future API-fetch'а (Phase B/1 паттерн).
DashboardPageHead использует Vue 3.5 defineModel<T>() для двусторонней привязки range.
Sub-components читают только props — без Pinia stores (mock-data flow).
Все sub-components <250 строк (acceptance threshold). Shell line counts: 178/84.
ЗАМЕЧАНИЕ по acceptance «0 components >300»: НЕ закрыто полностью. 2 файла остались
выше порога — DealsView 560 + DealDetailDrawer 386. Зафиксировано в Sprint 3 Phase C
commit 6c2f0ce : bulk-action функции (applyBulkStatus/applyBulkDelete/applyBulkExport/
undoBulkDelete/applyBulkRestoreFromTrash) и comment/reminders fetch экспонируются
через defineExpose в Vitest-тестах напрямую — дальнейшая декомпозиция требует
изменения тест-контракта (отдельным flow, не вошло в Sprint 4 Phase B).
Phase B/3 закрывает 8/12 audit-кандидатов O-refactor-04: 3 в Sprint 3 Phase C
(Top-3) + 5 в Sprint 4 Phase B/1+B/2 (admin/layout/billing/security/reminders) +
2 в B/3 (errors/dashboard). Оставшиеся: 2 крупных deals-view (defineExpose-blocked)
+ ImpersonationDialog уже <300 органически.
Регрессия: ESLint 0 + vue-tsc 0 + Vitest 416/416 + build OK 989 ms.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-10 04:53:09 +03:00
Дмитрий
849bc73290
refactor(frontend): Sprint 4 Phase B/2 — split 3 user views (audit O-refactor-04 хвост)
...
BillingView 416→114 (+ BalanceCard 155 + TransactionsTable 113 + InvoicesTable 90
+ billingFormatters 51 composable: formatPlain/formatCost/statusChipColor/
statusLabel/formatLabel/formatIcon/txAmountClass).
SecurityTab 354→39 (+ ChangePasswordCard 17 + TwoFactorCard 218 + RecoveryCodesCard 104
+ SessionsTable 66; auth-store читается напрямую в каждом sub-component).
RemindersView 345→183 (+ RemindersFilters 51 + RemindersList 173;
ReminderDialog уже отдельный с прошлой фазы — служит как ReminderForm).
State (`activeTab`, `editingReminder`, `deletingReminderId` в RemindersView)
остаётся в parent ради единого reload-flow + confirm-dialog'ов. Auth-store
читается напрямую в TwoFactorCard/RecoveryCodesCard через useAuthStore() —
без prop-drilling. Reminders-store читается напрямую в RemindersFilters/
RemindersList.
Все sub-components <250 строк (acceptance threshold). 3 view-shells: 114/39/183.
Регрессия: ESLint 0 + vue-tsc 0 + Vitest 416/416 + build OK 968 ms.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-10 04:46:14 +03:00
Дмитрий
30ef61dff8
refactor(frontend): Sprint 4 Phase B/1 — split 3 admin/layout views (audit O-refactor-04 хвост)
...
3 view'а с >300 строк разделены на shell + sub-components:
AdminTenantsView 377→155 (+ TenantsStatsHeader 82 / TenantsFilters 93 / TenantsTable 116).
AdminTenantDetailView 436→109 (+ TenantDetailHeader 158 / TenantDetailTabs 176
+ adminTenantDetailFormatters 43 composable).
AppLayout 466→78 (+ AppSidebar 155 / AppTopbar 269; R0.6 hard-стоп снят
явным запросом заказчика 10.05.2026).
State (filterStatuses, tenantsState, activeTab, tenant, drawerOpen) остаётся
в parent view'ах ради `defineExpose`-контракта Vitest тестов. Sub-components
читают Pinia stores напрямую (auth + notifications + reminders) — без
prop-drilling.
AppTopbar 269 строк <300 — acceptance threshold выдержан (можно дальше split на
NotificationsDropdown + UserMenu в отдельном flow, не критично).
Регрессия: ESLint 0 + vue-tsc 0 + Vitest 416/416 + build OK 1.17 сек.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-10 04:38:08 +03:00
Дмитрий
6c2f0ce682
refactor(frontend): Sprint 3 Phase C — Top-3 Vue components split (audit O-refactor-04)
...
Sprint 3 Phase C. Закрытие audit O-refactor-04 (частичное — Top-3 из 12):
- DealsView.vue: 852 → 560 строк. Выделены: DealsFilters (123), DealsBulkBar (150),
DealsTable (165). CSV-utilities (csvEscape/triggerCsvDownload/triggerBlobDownload/
buildCsvString) вынесены в composables/useCsvDownload.ts.
- ReportsView.vue: 592 → 261 строк. Выделены: ReportRequestForm (тип отчёта,
даты, фильтры, формат, submit/reset), ReportJobsList (список заданий со
статусами и actions retry/cancel/delete).
- DealDetailDrawer.vue: 580 → 386 строк. Выделены: DealDetailHero (header +
phone-link + status-chip), DealDetailTimeline (activity log с MOCK_EVENTS).
Comment- и Reminders-секции оставлены inline — связаны с API и defineExpose.
DealsView и DealDetailDrawer остались выше 350-целевого уровня: bulk-action
функции (applyBulkStatus/applyBulkDelete/applyBulkExport/undoBulkDelete/
applyBulkRestoreFromTrash) и comment/reminders fetch — экспонируются через
defineExpose в Vitest-тестах напрямую, дальнейшая декомпозиция требует
изменения тест-контракта (отдельным flow).
Layout-структуры (AppLayout 466, AuthLayout, AppShell) НЕ ТРОНУТЫ — R0.6 hard-стоп.
Остальные 9 components >300 строк (AdminTenantDetailView, BillingView,
AdminTenantsView, SecurityTab, RemindersView, ErrorView, DashboardView,
ImpersonationDialog, далее) — вне scope Sprint 3, отдельным flow по запросу.
vue-tsc: 0 errors. ESLint: 0. Vitest: 416/416 PASS. Build: success (1.15s).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-09 20:28:25 +03:00
Дмитрий
2d7d7d1188
feat(frontend): Sprint 2 Phase B — Vue 3.5 defineModel + Vuetify 3.12 typed slots + lazy-imports + ESLint check
...
Sprint 2 Phase B (modernization). Закрытие audit O-stack-04/05/07 + O-perf-06:
- O-stack-04: Vue 3.5 defineModel() в 3 диалогах (NewDealDialog,
ImpersonationDialog, ReminderDialog) — boilerplate −5 строк/файл.
+ useTemplateRef() в TwoFactorView (input v-for refs).
- O-stack-05: Vuetify 3.12 типизированные слоты VDataTable
(DealsView + AdminTenantsView) — inline-аннотации `{ item }: { item: T }`
на 6+7 scoped-slot bindings; vue-tsc проверяет доступ к полям статически.
- O-stack-07: ESLint flat-config verified — header-comment добавлен
в eslint.config.js. Legacy .eslintrc.json не используется.
- O-perf-06: defineAsyncComponent() для тяжёлых диалогов в 3 местах:
DealsView (DealDetailDrawer + NewDealDialog), DealDetailDrawer
(ReminderDialog), RemindersView (ReminderDialog). KanbanView оставлен
sync — async-загрузка приводила к EnvironmentTeardownError в jsdom
(KanbanView.spec.ts), see in-file comment. Сборка показывает chunk'и
ImpersonationDialog (7.61 kB), DealDetailDrawer (11.12 kB), NewDealDialog
и ReminderDialog как отдельные lazy-bundles.
vue-tsc: 0 errors. ESLint: 0. Vitest: 416/416 PASS. Build: success.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-09 19:36:02 +03:00
Дмитрий
dc1457a008
phase2(reminders-frontend): RemindersView + DealDetailDrawer + nav-badge
...
P0 этап 5 — frontend для reminders (после backend-этапа 4).
Пользователь может создавать/просматривать/завершать/удалять напоминания
из UI с inline-create в DealDetailDrawer.
Frontend:
- api/reminders.ts: типизированные helpers для 5 endpoints + ensureCsrfCookie
для mutating. Types ReminderFilter/ApiReminder/ReminderCounts.
- stores/reminders.ts: Pinia с items/counts/currentFilter +
load/refreshCounts/create/update/complete/remove. Optimistic для
complete/remove с revert на reject.
- components/reminders/ReminderDialog.vue: dual-mode (create/edit) modal
с native datetime-local input. Props dealId?/reminder? (edit),
ISO-конверсия при submit.
- views/RemindersView.vue: page-head с stats (active/overdue) + reload-btn;
4 tabs (today/upcoming/overdue/completed) с counts на бейджах
(overdue=error color); v-list с complete-btn / dropdown
Изменить/Удалить с confirm-dialog; empty-state.
- router: /reminders маршрут (lazy).
- AppLayout: nav-badge «Напоминания» биндит count из store
(replace static «12»); скрыт при count=0; polling 60 сек для
refreshCounts.
- DealDetailDrawer: секция «Напоминания» (только при tenantId+deal):
inline + create-btn / список / complete / встроенный ReminderDialog.
Vitest +20 (369/369 за 21.20 сек):
- reminders-store 11: initial / load+reject / refreshCounts /
create+reject / complete optimistic+revert / remove+reject / reset.
- RemindersView 7: mount / 4 tabs / counts / empty-state /
список / reload-btn / filter=today default.
- AppLayout +2: бейдж скрыт при count=0 / показывает count при >0.
Pest 347/347 (без изменений — backend нетронут).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-09 12:41:41 +03:00
Дмитрий
7e1bf8b42d
phase2(deal-patch): PATCH /api/deals/{id} + comment-editor в DealDetailDrawer (этап 1/5)
...
Drawer из read-only становится editable. ActivityLog event пишется на
каждое изменение поля.
Backend (DealController::update):
- PATCH /api/deals/{id} {tenant_id, comment?, manager_id?, status?}.
- Каждое изменённое поле → ActivityLog:
comment → deal.commented (context.text);
manager_id → deal.assigned (context.from/to + assigned_at=NOW);
status → deal.status_changed (context.from/to/source='manual').
- NO-OP не пишется в audit. Manager FK guard + status slug validation.
- RLS + defense-in-depth where(tenant_id) → 404 для чужой сделки.
Pest +10 (DealUpdateTest):
- 422/404 базовые / 404 чужая сделка / comment+audit / manager+audit+
assigned_at / status+audit / 422 неизвестный slug / 422 чужой manager /
NO-OP не пишет / комбинированно → 2 audit записи.
Frontend:
- api/deals.ts::updateDeal — PATCH helper c ensureCsrfCookie.
- DealDetailDrawer: новая секция «Комментарий» (только при tenantId).
v-textarea auto-grow + counter=5000 + Save-btn → updateDeal →
toast success + reload events (новый deal.commented в timeline).
На fail → warning toast.
Vitest +3 (DealDetailDrawerApi):
- saveComment вызывает updateDeal + toast + reload events (getDeal x2).
- saveComment reject → commentSaveError + warning toast.
- comment-section не рендерится без tenantId.
PHPStan baseline регенерирован.
Регресс:
- Lint+type-check+format passed.
- Vitest 283/283 за 18.13 сек (+3 от 280).
- Vite build 1.12 сек.
- Pint + PHPStan passed.
- Pest 220/220 за 25.64 сек (+10 от 210, 871 assertion).
Реестр v1.64→v1.65 / CLAUDE.md v1.55→v1.56.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-09 09:10:58 +03:00
Дмитрий
e31ea5354a
phase2(lead-statuses): GET /api/lead-statuses + Pinia-store с snapshot fallback
...
Заменяет static-снапшот LEAD_STATUSES в коде на live-данные из БД.
Custom slug'и (добавленные после deployment'а) теперь видны UI без rebuild'а.
Backend:
- LeadStatus model (PK=slug string, incrementing=false, timestamps=null).
- LeadStatusController::index — GET /api/lead-statuses, ORDER BY sort_order,
slug. Таблица глобальная (не tenant-aware), auth не требуется на MVP.
Pest +5 (LeadStatusesIndexTest):
- 200 + не пустой / все 14 системных slug'ов из seed / все нужные поля /
sort_order ASC / кастомный slug после INSERT появляется в endpoint'е.
Frontend:
- api/leadStatuses.ts::listLeadStatuses — GET helper.
- stores/leadStatuses.ts::useLeadStatusesStore — Pinia setup-store:
statuses default = LEAD_STATUSES snapshot (UI работает без fetch'а),
load(force=false) идемпотентен, bySlug computed Map, findBySlug helper.
На fail — snapshot остаётся, fetchError=true.
- DealsView/KanbanView/DealDetailDrawer переехали со static-импорта
LEAD_STATUSES на store. KanbanView использует safe-access
dealsByStatus[slug] || [] (защита от custom slug'а из API без seeded
column). load() в onMounted у обоих view'ов.
Vitest +7 (leadStatusesStore.spec.ts):
- initial snapshot / findBySlug existing & null / load success replace +
loaded / load reject — fetchError + snapshot fallback / load идемпотентен /
load(force=true) refetch.
- 2 spec'а DealDetailDrawer получили setActivePinia(createPinia()) в
beforeEach (без этого Pinia store-injection в jsdom падает).
PHPStan baseline регенерирован.
Регресс:
- Lint+type-check+format passed.
- Vitest 280/280 за 19.44 сек (+7 от 273).
- Vite build 1.17 сек.
- Pint + PHPStan passed.
- Pest 210/210 за 24.59 сек (+5 от 205, 840 assertions).
Реестр v1.63→v1.64 / CLAUDE.md v1.54→v1.55.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-09 08:59:17 +03:00