# Quiet Luxury редизайн элементов #1440 + #896 — 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:** перевести два визуально выпадающих элемента (`span.card-check__box` в `ProjectCard.vue` и `v-text-field` «Название проекта» в `NewProjectDialog.vue`) на язык токенов `tokens.css` (Quiet Luxury I1) — без новых компонентов, без правок токенов, без правок API/store/валидации.

**Architecture:** CSS-only правка scoped-стилей для #1440 + Vuetify-prop расширение + scoped `:deep()` override для #896. Имена классов и template-структура сохраняются → существующие Vitest behavior-тесты и dev-indices manifest не ломаются.

**Tech Stack:** Vue 3.5, Vuetify 3.12, Vite, Vitest 4.1, vue-tsc, ESLint flat-config 10, Histoire 1.0-beta.1, lefthook 2.1, Pa11y (optional smoke).

**Spec:** [docs/superpowers/specs/2026-05-12-quiet-luxury-elements-1440-896-design.md](../specs/2026-05-12-quiet-luxury-elements-1440-896-design.md) (commit `7d86971`).

**Branch:** `plan5-frontend-projects` (текущая active).

---

## File Structure

**Modified (2 files):**

| Путь | Что меняется | Зачем |
|---|---|---|
| `app/resources/js/components/projects/ProjectCard.vue` | Полная замена `<style scoped>` (lines 132–172). Template и script нетронуты. | Применить `--liderra-line` / `--radius-6` / tonal teal checked-стейт + добавить `:focus-visible`. |
| `app/resources/js/views/projects/NewProjectDialog.vue` | Расширить props у `<v-text-field>` на line 59 (variant/density/color/rounded/class). Добавить новый `<style scoped>` блок в конец файла (после `</script>` на line 208). | Перевести «Название проекта» с filled на outlined под палитру токенов; уникальный `.ld-input-quiet` локализует override. |

**Not modified:** `tokens.css`, `app.css`, `motion.css`, `typography.css`, `vuetify.ts`, `dev-indices.json`, `ProjectsView.vue` (toolbar checkbox + filter bar — out of scope per spec §5).

**Not created:** новые primitive-компоненты (`LdTextField` / `LdCheckbox`) — out of scope.

---

## Task 0: Pre-flight verification (риски §6 спеки)

**Files:** read-only.

- [ ] **Step 0.1: Re-read `app/resources/js/plugins/vuetify.ts`** — подтвердить, что Vuetify theme определяет `primary: '#0F6E56'` (или `var(--liderra-teal)`). Это закрывает риск §6.4 спеки.

Run:

```bash
cat app/resources/js/plugins/vuetify.ts
```

Expected: в `createVuetify({ theme: { themes: { ... primary: ... } } })` значение primary совпадает с `#0F6E56` (`--liderra-teal`). Если не совпадает — STOP, согласовать со спекой / заказчиком, не имплементить.

- [ ] **Step 0.2: Проверить `rounded="lg"` mapping в Vuetify 3.12** — закрывает риск §6.3 спеки.

Run:

```bash
grep -rn "rounded" node_modules/vuetify/lib/composables/rounded.* 2>/dev/null | head -20
```

или вызов через context7:

```
mcp__plugin_context7_context7__query-docs "vuetify 3 rounded prop lg value pixels"
```

Expected: `lg` соответствует `8px` border-radius. Если значение другое — добавить в Task 2 CSS override `:deep(.v-field) { border-radius: var(--radius-8); }` явно.

- [ ] **Step 0.3: Найти Vitest spec-файлы для затрагиваемых компонентов**

Run:

```bash
find app/resources/js -type f \( -name "ProjectCard*.spec.ts" -o -name "ProjectCard*.test.ts" -o -name "NewProjectDialog*.spec.ts" -o -name "NewProjectDialog*.test.ts" \) 2>/dev/null
```

Expected: нули или несколько путей. Зафиксировать список как `$TESTED_SPECS` для последующих task'ов. Если spec'и не найдены — записать в notes; пропустить шаги «run targeted Vitest» и переключиться на manual smoke (Task 5).

- [ ] **Step 0.4: Найти Histoire-stories для затрагиваемых компонентов**

Run:

```bash
find app/resources/js -type f \( -name "ProjectCard.story.vue" -o -name "NewProjectDialog.story.vue" \) 2>/dev/null
```

Expected: нули или 1–2 пути. Зафиксировать как `$STORIES`. Если stories нет — Histoire-smoke step в Task 5 → skipped (с явной отметкой в commit message).

- [ ] **Step 0.5: Зафиксировать baseline Vitest**

Run:

```bash
cd app && npm run test:vue 2>&1 | tail -60
```

Expected: green (per memory `project_state.md` — 594 passed / 3 skipped / 74 files на текущей ветке). Зафиксировать ровно столько pass/skip/fail — это baseline, не должен регрессировать после правок. Если baseline уже red — STOP, починить baseline отдельным task'ом до начала.

---

## Task 0.5b: Pre-baseline fix — починить `ProjectCard.spec.ts:43`

**Files:**

- Modify: `app/tests/Frontend/ProjectCard.spec.ts:42`

**Подход:** Pre-existing failing test (commit `c9ee8d8`, см. spec §6.6). `data-testid="card-select"` на `<label>`, но `@change` handler на `<input>` внутри (ProjectCard.vue:9). В jsdom change-event на label не bubble. Fix: trigger на input через CSS-селектор. Atomic commit перед Task 1, чтобы baseline стал зелёным.

- [ ] **Step 0.5b.1: Edit `ProjectCard.spec.ts:42`**

Заменить:

```ts
await wrapper.find('[data-testid="card-select"]').trigger('change');
```

на:

```ts
await wrapper.find('[data-testid="card-select"] input').trigger('change');
```

- [ ] **Step 0.5b.2: Targeted Vitest run на ProjectCard.spec**

Run (из корня репо):

```bash
cd app && npx vitest run tests/Frontend/ProjectCard.spec.ts --reporter=verbose 2>&1 | tail -30
```

Expected: 4 passed (раньше 3 passed / 1 failed).

- [ ] **Step 0.5b.3: Stage + atomic commit**

```bash
git add app/tests/Frontend/ProjectCard.spec.ts
git commit -m "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.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
```

- [ ] **Step 0.5b.4: Re-baseline Vitest**

Run:

```bash
cd app && npm run test:vue 2>&1 | tail -10
```

Expected: `614 passed | 3 skipped | 0 failed (617)` / 79 files. Если что-то ещё red — STOP, разобраться (применить systematic-debugging skill).

---

## Task 1: ProjectCard #1440 — заменить `<style scoped>`

**Files:**

- Modify: `app/resources/js/components/projects/ProjectCard.vue:132-172`

**Подход:** CSS-only. Поведение, emit'ы и атрибуты `data-testid` сохраняются → существующие behavior-тесты пройдут без правок (за исключением snapshot-тестов, если они есть; их обновляем как intended).

- [ ] **Step 1.1: Открыть файл и заменить блок `<style scoped>`**

Edit `app/resources/js/components/projects/ProjectCard.vue`, заменить ровно строки 132–172 (от `<style scoped>` до `</style>` включительно) на:

```vue
<style scoped>
.project-card.paused {
    opacity: 0.75;
}
.card-check {
    display: inline-flex;
    align-items: center;
    cursor: pointer;
    padding: 4px;
}
.card-check input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}
.card-check__box {
    width: 16px;
    height: 16px;
    border: 1px solid var(--liderra-line);
    border-radius: var(--radius-6);
    background: var(--liderra-surface);
    display: inline-block;
    position: relative;
    transition:
        border-color 200ms cubic-bezier(0.16, 1, 0.3, 1),
        background-color 200ms cubic-bezier(0.16, 1, 0.3, 1);
}
.card-check:hover .card-check__box {
    border-color: var(--liderra-line-strong);
}
.card-check input:focus-visible + .card-check__box {
    outline: 2px solid var(--liderra-teal);
    outline-offset: 2px;
}
.card-check input:checked + .card-check__box {
    background: rgba(15, 110, 86, 0.10);
    border-color: var(--liderra-teal);
}
.card-check input:checked + .card-check__box::after {
    content: '';
    position: absolute;
    left: 4px;
    top: 0;
    width: 5px;
    height: 9px;
    border: solid var(--liderra-teal);
    border-width: 0 1.5px 1.5px 0;
    transform: rotate(45deg);
}
</style>
```

- [ ] **Step 1.2: ESLint на изменённый файл**

Run:

```bash
cd app && npx eslint resources/js/components/projects/ProjectCard.vue 2>&1
```

Expected: 0 errors / 0 warnings. Если есть — пофиксить ДО продолжения.

- [ ] **Step 1.3: vue-tsc на изменённый файл (через full type-check)**

Run:

```bash
cd app && npm run type-check 2>&1 | tail -30
```

Expected: green. CSS-only правка не должна задеть типы, но проверяем (на случай если scoped-стиль сломал что-то структурное).

- [ ] **Step 1.4: Targeted Vitest run на ProjectCard.spec, если найден в Task 0.3**

Run (заменив `$SPEC_PATH` на найденный путь):

```bash
cd app && npx vitest run "$SPEC_PATH" --reporter=verbose 2>&1 | tail -40
```

Expected: green. Если behavior-spec'и пройдут — отлично. Если есть snapshot-тесты и они зафейлились на CSS-diff — это **intended change**, обновить snapshot'ы:

```bash
cd app && npx vitest run "$SPEC_PATH" -u 2>&1 | tail -40
```

Re-run без `-u` чтобы убедиться зелёный после update:

```bash
cd app && npx vitest run "$SPEC_PATH" --reporter=verbose 2>&1 | tail -40
```

Если spec-файл не найден в Task 0.3 — skip с явной отметкой и переход к Step 1.5.

- [ ] **Step 1.5: Stage файл (без коммита — коммит общий после Task 2)**

Run:

```bash
git add app/resources/js/components/projects/ProjectCard.vue
git status --short app/resources/js/components/projects/ProjectCard.vue
```

Expected: `M  app/resources/js/components/projects/ProjectCard.vue`. Если есть staged snapshot — также staged (`git add` его явно).

---

## Task 2: NewProjectDialog #896 + 4 близнеца — `class="ld-input-quiet"` на 5 v-text-field + `<style scoped>`

**Files:**

- Modify: `app/resources/js/views/projects/NewProjectDialog.vue` lines 21, 30, 48, 59, 61 (template — добавить `class="ld-input-quiet"` к каждому v-text-field)
- Modify: `app/resources/js/views/projects/NewProjectDialog.vue:208+` (новый `<style scoped>` блок в конец файла)

**Подход:** Vuetify global default из [vuetify.ts:50-54](../../app/resources/js/plugins/vuetify.ts#L50-L54) уже делает все v-text-field outlined с color=primary (теал-teal на focus). Достаточно добавить класс `.ld-input-quiet` к 5 нужным полям и один scoped CSS-блок, который переписывает border-color (4-state) и border-radius через `:deep()`.

- [ ] **Step 2.1: Добавить `class="ld-input-quiet"` ко всем 5 v-text-field**

Edit `app/resources/js/views/projects/NewProjectDialog.vue`. Пять точечных правок:

**Line 21 (Домен сайта):**

```vue
<v-text-field
    v-model="form.signal_identifier"
    label="Домен конкурента"
    placeholder="okna-konkurent.ru"
    :readonly="mode === 'edit'"
    :error-messages="errors.signal_identifier"
/>
```

→ добавить атрибут `class="ld-input-quiet"`:

```vue
<v-text-field
    v-model="form.signal_identifier"
    label="Домен конкурента"
    placeholder="okna-konkurent.ru"
    :readonly="mode === 'edit'"
    class="ld-input-quiet"
    :error-messages="errors.signal_identifier"
/>
```

**Line 30 (Номер звонка):** Аналогично — добавить `class="ld-input-quiet"` в существующий v-text-field перед `:error-messages`.

**Line 48 (Ключевое слово SMS):** Аналогично.

**Line 59 (Название проекта):**

```vue
<v-text-field v-model="form.name" label="Название проекта" :error-messages="errors.name" />
```

→

```vue
<v-text-field v-model="form.name" label="Название проекта" class="ld-input-quiet" :error-messages="errors.name" />
```

**Line 61 (Лимит лидов в день):** Аналогично — добавить `class="ld-input-quiet"`.

После всех 5 правок проверить, что:

```bash
grep -c 'class="ld-input-quiet"' app/resources/js/views/projects/NewProjectDialog.vue
```

выводит ровно `5`. И что:

```bash
grep -c '<v-text-field' app/resources/js/views/projects/NewProjectDialog.vue
```

выводит `5` (все 5 v-text-field есть, ни одного дополнительного / пропущенного).

- [ ] **Step 2.2: Добавить новый `<style scoped>` блок в конец файла**

Edit `app/resources/js/views/projects/NewProjectDialog.vue`, добавить ПОСЛЕ строки `</script>` (line 208) новый блок:

```vue

<style scoped>
.ld-input-quiet :deep(.v-field) {
    border-radius: var(--radius-8);
}
.ld-input-quiet :deep(.v-field__outline__start),
.ld-input-quiet :deep(.v-field__outline__end),
.ld-input-quiet :deep(.v-field__outline__notch::before),
.ld-input-quiet :deep(.v-field__outline__notch::after) {
    border-color: var(--liderra-line);
    opacity: 1;
    transition: border-color 200ms cubic-bezier(0.16, 1, 0.3, 1);
}
.ld-input-quiet :deep(.v-field:hover .v-field__outline__start),
.ld-input-quiet :deep(.v-field:hover .v-field__outline__end),
.ld-input-quiet :deep(.v-field:hover .v-field__outline__notch::before),
.ld-input-quiet :deep(.v-field:hover .v-field__outline__notch::after) {
    border-color: var(--liderra-line-strong);
    opacity: 1;
}
.ld-input-quiet :deep(.v-field--focused .v-field__outline__start),
.ld-input-quiet :deep(.v-field--focused .v-field__outline__end),
.ld-input-quiet :deep(.v-field--focused .v-field__outline__notch::before),
.ld-input-quiet :deep(.v-field--focused .v-field__outline__notch::after) {
    border-color: var(--liderra-teal);
    opacity: 1;
}
.ld-input-quiet :deep(.v-field--error:not(.v-field--disabled) .v-field__outline__start),
.ld-input-quiet :deep(.v-field--error:not(.v-field--disabled) .v-field__outline__end),
.ld-input-quiet :deep(.v-field--error:not(.v-field--disabled) .v-field__outline__notch::before),
.ld-input-quiet :deep(.v-field--error:not(.v-field--disabled) .v-field__outline__notch::after) {
    border-color: currentColor;
    opacity: 1;
}
</style>
```

(Пустая строка перед `<style scoped>` для отделения от `</script>`.)

**Almost-trap-avoidance заметки** (from Task 2 code review):

- `opacity: 1` обязателен в каждом override-блоке — иначе наследуется Vuetify's `--v-field-border-opacity: 0.38` и `--liderra-line` (alpha 0.08) → effective alpha 0.03 (invisible).
- Hover должен быть `.v-field:hover` ВНУТРИ `:deep()`, не `.ld-input-quiet:hover` на root — root класс на `.v-input` включает hint/error-message-область.
- Error-стейт rule обязателен — без него override блокирует `currentColor`-наследование от Vuetify error-themable color, и красный error-border никогда не появляется на 5 валидируемых полях.

- [ ] **Step 2.3: ESLint на изменённый файл**

Run:

```bash
cd app && npx eslint resources/js/views/projects/NewProjectDialog.vue 2>&1
```

Expected: 0 errors / 0 warnings.

- [ ] **Step 2.4: vue-tsc full type-check**

Run:

```bash
cd app && npm run type-check 2>&1 | tail -30
```

Expected: green. `class` атрибут — стандартный Vue prop, типизирован.

- [ ] **Step 2.5: Targeted Vitest run на NewProjectDialog.spec и EditProjectDialog.spec**

Run:

```bash
cd app && npx vitest run tests/Frontend/NewProjectDialog.spec.ts tests/Frontend/EditProjectDialog.spec.ts --reporter=verbose 2>&1 | tail -40
```

Expected: green. Изменения template:

- Behavior-тесты (tab rendering, sms-tab-switching, validation guard, submit button presence) — не зависят от class и border-color, должны пройти.
- Snapshot-тесты — отсутствуют в этих spec'ах (Task 0.3 показал — никаких `.snap` файлов нет), regression-риска нет.

Если что-то red — STOP, применить systematic-debugging:

1. Hypothesis 1: класс `ld-input-quiet` ломает `text()` assertions (маловероятно — у Vuetify класс не выводится в текст).
2. Hypothesis 2: новый `<style scoped>` блок ломает SFC-парсинг (проверить через `cat` файла).
3. Hypothesis 3: `:deep(.v-field) { border-radius }` конфликтует с Vuetify-internal CSS-cascade в jsdom (маловероятно — jsdom не render'ит CSS, но проверить).

- [ ] **Step 2.6: Stage файл**

Run:

```bash
git add app/resources/js/views/projects/NewProjectDialog.vue
git status --short app/resources/js/views/projects/NewProjectDialog.vue
```

Expected: `M  app/resources/js/views/projects/NewProjectDialog.vue` (плюс staged snapshot'ы если были обновлены).

---

## Task 3: Full Vitest regression

**Files:** none modified, run-only.

- [ ] **Step 3.1: Full Vitest run**

Run:

```bash
cd app && npm run test:vue 2>&1 | tail -80
```

Expected: pass count ≥ baseline из Task 0.5 (≥594 passed / 3 skipped / 74 files, минус snapshot-fails если они уже обновлены в Task 1/2). Failures выписать дословно с file:line.

Если регрессия (тест, который проходил в Task 0.5, теперь fail) — STOP. Применить systematic-debugging skill:

1. Hypothesis 1: scoped-стиль сломал className в render (DOM-assertion fail).
2. Hypothesis 2: Vuetify outlined-variant добавил extra wrapper-div, ломающий `findAll(...)` индексы.
3. Hypothesis 3: snapshot не был обновлён (`-u` не сработал) — re-run с `-u`.
Falsify по очереди, fix, re-run Step 3.1.

- [ ] **Step 3.2: Full ESLint**

Run:

```bash
cd app && npm run lint:vue 2>&1 | tail -20
```

Expected: green.

- [ ] **Step 3.3: Full vue-tsc (если ещё не делали полностью в Task 2.4)**

Run:

```bash
cd app && npm run type-check 2>&1 | tail -20
```

Expected: green.

---

## Task 4: Histoire smoke (опционально, по результатам Task 0.4)

**Files:** none modified, run-only.

- [ ] **Step 4.1: Histoire build (если есть `$STORIES` из Task 0.4)**

Run:

```bash
cd app && npm run story:build 2>&1 | tail -40
```

Expected: build success, без ошибок компиляции `.story.vue`. Если story-файлов нет — пропустить с отметкой в commit message.

- [ ] **Step 4.2: Histoire dev preview (опциональный визуальный smoke)**

Run в фоне:

```bash
cd app && npm run story 2>&1
```

Открыть http://localhost:6006 (или порт по конфигу `histoire.config.ts`), вручную пройти по state'ам `ProjectCard.story.vue` (idle / hover / focus / checked / partial) и `NewProjectDialog.story.vue` (rest / hover / focus / error) — визуально подтвердить Quiet Luxury стиль. Записать наблюдения. Это опциональный шаг, не блокер.

---

## Task 5: Manual browser smoke

**Files:** none modified, run-only.

- [ ] **Step 5.1: Запустить dev-сервера в фоне**

Run в двух разных background bash:

```bash
cd app && php artisan serve
```

```bash
cd app && npm run dev
```

Expected: artisan на `http://127.0.0.1:8000`, Vite на `http://localhost:5173`. См. memory `feedback_environment.md` Plan 5 quirks (SESSION_DRIVER=file + seed Tenant).

- [ ] **Step 5.2: Открыть в браузере и проверить визуально**

Открыть `http://127.0.0.1:8000/projects` (Playwright MCP или вручную). Аутентифицироваться (demo-tenant — см. memory `project_supplier_integration.md`).

Проверочный лист:

- **#1440 idle:** карточки проекта — checkbox-бокс 16×16 с тонкой `--liderra-line` границей, фон белый.
- **#1440 hover:** на hover карточки или mouseover самого бокса — граница становится `--liderra-line-strong` (чуть видимее).
- **#1440 keyboard focus:** Tab до checkbox → видимый teal-outline 2px вокруг бокса (focus-visible).
- **#1440 checked:** клик → tonal teal-заливка (~`#E7F1ED`) + teal-stroke галочки, граница teal.
- **5 v-text-field idle (site/call/sms tabs + name + daily limit):** «+ Создать проект» → каждое поле имеет 1px тонкую `--liderra-line` рамку, радиус 8px, label сверху (floating).
- **5 v-text-field hover:** hover на каждое поле → рамка `--liderra-line-strong`.
- **5 v-text-field focus:** клик в каждое поле → рамка `--liderra-teal`, label и caret teal.
- **Validation:** оставить «Название проекта» пустым, нажать «Создать» → должна появиться error-message от 422-валидации; рамка в error-стейте управляется Vuetify (теаловый теплёт error → красный, не override-им).
- **Контраст: combobox / autocomplete / btn-toggle остаются с дефолтным border-цветом** — это ожидаемо (out-of-scope).

Если на одном из чек-боксов визуал не сошёлся со спекой — записать наблюдение в notes и применить systematic-debugging.

- [ ] **Step 5.3: Остановить dev-сервера**

Background-процессы из Step 5.1 остановить (Ctrl+C / kill).

---

## Task 6: Pre-commit + commit + push

**Files:** all staged from Task 1.5 + Task 2.6 (+ snapshot-updates если были).

- [ ] **Step 6.1: Сверить staged diff**

Run:

```bash
git diff --cached --stat
git diff --cached
```

Expected: только 2 файла + опционально snapshot'ы:

- `app/resources/js/components/projects/ProjectCard.vue` — изменения в `<style scoped>`.
- `app/resources/js/views/projects/NewProjectDialog.vue` — добавлены props на line 59 + новый `<style scoped>` в конце.
- Возможные snapshot'ы из `__snapshots__/` папок.

НЕ должны быть staged: остальные dirty файлы из workspace (`app/dev-indices.json`, `app/resources/js/views/ProjectsView.vue`, png-скриншоты в корне).

- [ ] **Step 6.2: Запустить полный lefthook pre-commit run вручную**

Run:

```bash
npx lefthook run pre-commit 2>&1 | tail -40
```

Expected: все jobs green:

- gitleaks ✔
- markdownlint (skip — нет .md в staged)
- cspell (skip — нет .md в staged)
- stylelint (skip — нет .html в staged)
- pint (skip — нет .php в staged)
- larastan (skip — нет .php в staged)
- squawk (skip — нет .sql в staged)
- eslint-vue ✔ (ProjectCard.vue + NewProjectDialog.vue).

Если eslint-vue fail — пофиксить, re-stage, re-run.

- [ ] **Step 6.3: Атомарный commit**

Run:

```bash
git commit -m "$(cat <<'EOF'
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.

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
```

Lefthook сработает ещё раз на коммите — повторение Step 6.2 для надёжности; должен пройти.

- [ ] **Step 6.4: Verify commit landed**

Run:

```bash
git log -1 --stat
```

Expected: один новый commit на `plan5-frontend-projects`, 2 (или 2 + N snapshot) файла изменены.

- [ ] **Step 6.5: Pre-push hook (lefthook full gitleaks + lychee) — manual dry run**

Run:

```bash
npx lefthook run pre-push 2>&1 | tail -30
```

Expected:

- `gitleaks-full-history` ✔ (no leaks across history).
- `lychee-links` ✔ (existing markdown links OK).

Если что-то fail — STOP, разобраться. **НЕ push'ить с битыми ссылками или leaks.**

- [ ] **Step 6.6: Push на удалённую ветку**

Run:

```bash
git push origin plan5-frontend-projects 2>&1 | tail -10
```

Expected: push success. Memory `reference_github.md` подтверждает что `origin/plan5-frontend-projects` существует (последний known HEAD `9a7615b`, после спеки `7d86971`, после implementation +1 commit).

---

## Verification before completion

Перед claim «done» обязательно пройти через **superpowers:verification-before-completion** skill. Чек-лист:

1. [ ] Baseline fix commit (`test(projects): fix ProjectCard change-trigger target`) landed перед impl-коммитом.
2. [ ] `git status` чистый по 3 целевым файлам (`ProjectCard.spec.ts`, `ProjectCard.vue`, `NewProjectDialog.vue`).
3. [ ] Все 7 task'ов выше отмечены `[x]` (Task 0 + Task 0.5b + Task 1..6).
4. [ ] Vitest 614 passed / 3 skipped / 0 failed (Task 3.1).
5. [ ] vue-tsc 0 errors (Task 3.3).
6. [ ] ESLint 0 errors (Task 3.2).
7. [ ] Manual smoke (Task 5.2) — все 4 стейта #1440 + 4 стейта 5 v-text-field подтверждены глазами.
8. [ ] lefthook pre-commit + pre-push зелёные.
9. [ ] Импл-коммит с правильным conventional message landed (`feat(projects-ui): ...`).
10. [ ] Push на `origin/plan5-frontend-projects` подтверждён `git ls-remote`.

Только после прохождения всех 10 пунктов — финальный отчёт пользователю.

---

## Rollback plan

Если что-то пойдёт не так после push:

```bash
# Откатить commit локально и на remote
git revert <commit-sha>
git push origin plan5-frontend-projects
```

или (если push ещё не сделан):

```bash
git reset --hard HEAD~1
```

CSS-only правка изолирована — никаких миграций, никаких schema-изменений, никакого API. Rollback безопасен.

---

## Notes / not verified

- **Vuetify 3.12 internal class names** — `.v-field__outline__start/end/notch::before/::after` подтверждены по официальной документации Vuetify 3.x, но **не верифицировал** их присутствие в `node_modules/vuetify@3.12.0/` локально. Если `:deep()` селекторы не сработают — выполнить:

  ```bash
  grep -rn "v-field__outline" node_modules/vuetify/lib/components/VField/ | head -20
  ```

  и адаптировать селекторы под фактические имена.
- **Snapshot-тесты для ProjectCard и NewProjectDialog** — отсутствуют (Task 0.3 проверил: spec'и в `app/tests/Frontend/`, snapshot-папок нет). Risk зачёркнут.
- **Histoire story-файлы** — присутствуют: `ProjectCard.story.vue` + `NewProjectDialog.story.vue` (Task 0.4). Task 4 Histoire build применим.
- **Re-scope finding (Task 0.1)** — Vuetify global default в [vuetify.ts:50-54](../../app/resources/js/plugins/vuetify.ts#L50-L54) уже устанавливает `variant=outlined density=comfortable color=primary` для всех VTextField. Spec §1.2 «variant=filled по умолчанию» оказался неверен, спека и план переписаны (Commit 1 в sequence). Task 2 теперь применяет `.ld-input-quiet` ко всем 5 v-text-field в `NewProjectDialog.vue`, не только к #896.
- **Pre-existing baseline test failure** — Task 0.5b в плане фиксит `ProjectCard.spec.ts:43` отдельным atomic-коммитом перед impl Task 1. После fix baseline становится 614 / 3 / 0.
- **Pa11y a11y smoke** — `/projects` route требует auth, Pa11y без cookie-handling не зайдёт. Запускать только если есть auth-cookie-config (`pa11y-ci` JSON); иначе skip (manual focus-visible / contrast уже в Task 5).
- **Safari `:focus-visible` fallback** — не добавляем (spec §6.2 risk-decision). Safari < 15.4 не поддерживает `:focus-visible`, но share <1%. Если выявится конкретный пользовательский bug — добавить `.card-check input:focus + .card-check__box` дублирующий правило `:focus-visible` отдельным PR.
