docs(план): реализация глобального фикса позиционирования меню - 4 TDD-задачи
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Brain Status (auto-generated)
|
||||
|
||||
Last updated: 2026-06-27T04:18:13.211Z
|
||||
Last updated: 2026-06-27T07:09:55.632Z
|
||||
|
||||
| Контролёр | Состояние | Детали |
|
||||
|---|---|---|
|
||||
@@ -39,16 +39,16 @@ Last updated: 2026-06-27T04:18:13.211Z
|
||||
|
||||
| Время | Действие | Причина |
|
||||
|---|---|---|
|
||||
| 2026-06-27T07:03:56.852Z | bash:node -e "1" 2>/dev/null; for f in docs/secretary/*/protocol.md; do printf '%6s %s\n' "$(wc -l < "$f")" "$f"; done | floor: опасная по содержанию команда без аварийного выхода — блок (правило 8); FLOOR-ESCAPE: bash:node -e "1" 2>/dev/nul |
|
||||
| 2026-06-27T05:45:19.915Z | bash:rm ~/.claude/runtime/secretary-mode-*.json | floor: опасная по содержанию команда без аварийного выхода — блок (правило 8); FLOOR-ESCAPE: bash:rm ~/.claude/runtime/s |
|
||||
| 2026-06-27T04:18:53.490Z | powershell:Stop-Process -Id 4072 -Force | floor: опасная PowerShell-команда без аварийного выхода — блок (правило 8, V1-PS); FLOOR-ESCAPE: powershell:Stop-Process |
|
||||
| 2026-06-27T04:18:44.603Z | powershell:Stop-Process -Id 4072 -Force; if ($?) { "killed 4072" }; Start-Sleep -Milliseconds 300; Get-CimInstance Win32 | floor: опасная PowerShell-команда без аварийного выхода — блок (правило 8, V1-PS); FLOOR-ESCAPE: powershell:Stop-Process |
|
||||
| 2026-06-27T04:08:56.319Z | bash:cd "/c/Users/Administrator/.claude/runtime/" && for f in secretary-mode-*.json; do printf '%s => ' "$f"; cat "$f"; | floor: опасная по содержанию команда без аварийного выхода — блок (правило 8); FLOOR-ESCAPE: bash:cd "/c/Users/Administr |
|
||||
| 2026-06-27T04:07:22.527Z | bash:cd "c:/моя/проекты/claude-brain" && ls tools/ 2>&1 \| grep -i secret; echo "---grep config---"; grep -ril "secretar | floor: опасная по содержанию команда без аварийного выхода — блок (правило 8); FLOOR-ESCAPE: bash:cd "c:/моя/проекты/cla |
|
||||
| 2026-06-27T03:41:52.937Z | bash:node -e "const fs=require('fs'),os=require('os'),p=require('path');const dir=p.join(os.homedir(),'.claude','runtime | floor: опасная по содержанию команда без аварийного выхода — блок (правило 8); FLOOR-ESCAPE: bash:node -e "const fs=requ |
|
||||
| 2026-06-27T03:14:37.381Z | bash:find "c:\моя\проекты\claude-brain" -name "*sessionstart*" -type f 2>/dev/null | floor: опасная по содержанию команда без аварийного выхода — блок (правило 8); FLOOR-ESCAPE: bash:find "c:\моя\проекты\c |
|
||||
| 2026-06-27T03:10:25.034Z | bash:grep -n "globals\\|include\\|exclude" vitest.config.tools.mjs 2>/dev/null \|\| echo "NO tools config at root" | floor: опасная по содержанию команда без аварийного выхода — блок (правило 8); FLOOR-ESCAPE: bash:grep -n "globals\\|inc |
|
||||
| 2026-06-27T03:09:50.695Z | bash:git commit -F "C:/Users/ADMINI~1/AppData/Local/Temp/2/claude/c--------------claude-brain/2c98d47c-4f4d-48cc-98a8-2e | [verify-gate] расписка отклонена (stale-fingerprint) — пере-прогоните verify (staged-diff изменился / битая подпись) |
|
||||
| 2026-06-27T03:07:30.844Z | bash:git commit -F "C:/Users/ADMINI~1/AppData/Local/Temp/2/claude/c--------------claude-brain/2c98d47c-4f4d-48cc-98a8-2e | [verify-gate] расписка отклонена (stale-fingerprint) — пере-прогоните verify (staged-diff изменился / битая подпись) |
|
||||
| 2026-06-27T03:06:53.280Z | bash:git add tools/secretary-tap.mjs tools/secretary-tap.test.mjs tools/secretary-distill.mjs tools/secretary-distill.te | floor: опасная по содержанию команда без аварийного выхода — блок (правило 8); FLOOR-ESCAPE: bash:git add tools/secretar |
|
||||
| 2026-06-27T02:34:28.483Z | bash:ls -la ~/.claude/runtime/standby-* 2>/dev/null; echo "---"; cat ~/.claude/runtime/standby-mode-2c98d47c-4f4d-48cc-9 | floor: опасная по содержанию команда без аварийного выхода — блок (правило 8); FLOOR-ESCAPE: bash:ls -la ~/.claude/runti |
|
||||
| 2026-06-26T16:51:40.211Z | bash:git -C "c:/моя/проекты/claude-brain" add docs/superpowers/router-mentor-wall-GUIDE.md && git -C "c:/моя/проекты/cla | floor: опасная по содержанию команда без аварийного выхода — блок (правило 8); FLOOR-ESCAPE: bash:git -C "c:/моя/проекты |
|
||||
|
||||
## Метрики (информационные, не алерты)
|
||||
|
||||
@@ -132,9 +132,9 @@ Episodes since last run: 542 / threshold: 10
|
||||
|
||||
| PID | Имя | CPU-время | Возраст |
|
||||
|---|---|---|---|
|
||||
| 3440 | MsMpEng | 15.45ч | 220520.3ч |
|
||||
| 21928 | Code | 5.53ч | NaNч |
|
||||
| 1212 | svchost | 3.89ч | 0.0ч |
|
||||
| 3440 | MsMpEng | 15.63ч | 12285175.2ч |
|
||||
| 21928 | Code | 5.80ч | NaNч |
|
||||
| 1212 | svchost | 3.95ч | 0.0ч |
|
||||
|
||||
⚠️ Проверь, не «осиротевшие» ли это процессы от завершённых Claude-сессий.
|
||||
|
||||
@@ -150,7 +150,7 @@ Episodes since last run: 542 / threshold: 10
|
||||
|
||||
## Целостность журналов действий
|
||||
|
||||
🔴 Битые цепочки (3 из 119):
|
||||
🔴 Битые цепочки (3 из 120):
|
||||
|
||||
| session | broken at seq |
|
||||
|---|---|
|
||||
|
||||
@@ -0,0 +1,379 @@
|
||||
# Глобальный фикс позиционирования выпадающих меню — 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:** Включать обход бага позиционирования меню Vuetify автоматически и глобально, чтобы выпадающие списки не уезжали за экран ни в одном окне, и убрать ручные пометки.
|
||||
|
||||
**Architecture:** Один `MutationObserver` (ставится при запуске SPA из `app.ts`) ловит появление любого `.v-overlay.v-menu` в DOM и один раз запускает уже существующую стабилизацию позиции (`scheduleStabilize` — ядро прежнего `repositionMenuAfterOpen`). Ручные `@update:menu`/`@update:model-value` снимаются — остаётся одно место правды.
|
||||
|
||||
**Tech Stack:** Vue 3, Vuetify 3, TypeScript, Vitest + jsdom (`app/tests/Frontend/**`), vue-tsc, Vite.
|
||||
|
||||
**Спека:** [docs/superpowers/specs/2026-06-27-menu-reposition-global-fix-design.md](../specs/2026-06-27-menu-reposition-global-fix-design.md)
|
||||
|
||||
---
|
||||
|
||||
## Структура файлов
|
||||
|
||||
| Файл | Ответственность | Действие |
|
||||
|---|---|---|
|
||||
| `app/resources/js/utils/menuRepositionFix.ts` | стабилизация позиции меню + глобальный установщик | Modify |
|
||||
| `app/resources/js/app.ts` | точка запуска SPA — один вызов установщика | Modify |
|
||||
| `app/tests/Frontend/menuRepositionFix.spec.ts` | тест механизма (observer → один resize) | Create |
|
||||
| `app/resources/js/views/projects/NewProjectDialog.vue` | убрать ручной обход (регионы) + импорт | Modify |
|
||||
| `app/resources/js/components/projects/RegionsBulkDialog.vue` | убрать ×2 обход + импорт | Modify |
|
||||
| `app/resources/js/components/projects/ProjectDetailsDrawer.vue` | убрать ×1 обход + импорт | Modify |
|
||||
| `app/resources/js/components/layout/AppTopbar.vue` | убрать ×2 обход + импорт | Modify |
|
||||
|
||||
Все команды запускаются из каталога `app/` (там `package.json`). В PowerShell: `cd app; npm run ...`.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Глобальный установщик `installMenuRepositionFix` (TDD)
|
||||
|
||||
На этом шаге старый экспорт `repositionMenuAfterOpen` НЕ удаляем — он останется до Task 4, чтобы сборка оставалась зелёной (его ещё импортируют 4 окна). Добавляем новую функцию рядом, общее ядро `scheduleStabilize`.
|
||||
|
||||
**Files:**
|
||||
- Test: `app/tests/Frontend/menuRepositionFix.spec.ts` (создать)
|
||||
- Modify: `app/resources/js/utils/menuRepositionFix.ts`
|
||||
|
||||
- [ ] **Step 1: Написать падающий тест**
|
||||
|
||||
Создать `app/tests/Frontend/menuRepositionFix.spec.ts`:
|
||||
|
||||
```ts
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { installMenuRepositionFix } from '../../resources/js/utils/menuRepositionFix';
|
||||
|
||||
// Ручной requestAnimationFrame: кадры прогоняем детерминированно.
|
||||
let rafQueue: FrameRequestCallback[] = [];
|
||||
function flushFrames(n: number): void {
|
||||
for (let i = 0; i < n; i++) {
|
||||
const batch = rafQueue;
|
||||
rafQueue = [];
|
||||
batch.forEach((cb) => cb(0));
|
||||
}
|
||||
}
|
||||
|
||||
// Дать MutationObserver (микротаска jsdom) сработать.
|
||||
const tick = (): Promise<void> => new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
function makeMenu(): HTMLElement {
|
||||
const menu = document.createElement('div');
|
||||
menu.className = 'v-overlay v-menu';
|
||||
const content = document.createElement('div');
|
||||
content.className = 'v-overlay__content';
|
||||
menu.appendChild(content);
|
||||
return menu;
|
||||
}
|
||||
|
||||
let teardown: (() => void) | undefined;
|
||||
let rectWidth = 200;
|
||||
let rectLeft = 100;
|
||||
let origRect: typeof HTMLElement.prototype.getBoundingClientRect;
|
||||
let resizeSpy: ReturnType<typeof vi.fn>;
|
||||
|
||||
beforeEach(() => {
|
||||
rafQueue = [];
|
||||
rectWidth = 200;
|
||||
rectLeft = 100;
|
||||
vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => {
|
||||
rafQueue.push(cb);
|
||||
return rafQueue.length;
|
||||
});
|
||||
origRect = HTMLElement.prototype.getBoundingClientRect;
|
||||
HTMLElement.prototype.getBoundingClientRect = function (): DOMRect {
|
||||
return { width: rectWidth, height: 10, left: rectLeft, top: 0, right: rectLeft + rectWidth, bottom: 10, x: rectLeft, y: 0, toJSON: () => ({}) } as DOMRect;
|
||||
};
|
||||
resizeSpy = vi.fn();
|
||||
window.addEventListener('resize', resizeSpy);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
teardown?.();
|
||||
teardown = undefined;
|
||||
HTMLElement.prototype.getBoundingClientRect = origRect;
|
||||
window.removeEventListener('resize', resizeSpy);
|
||||
vi.unstubAllGlobals();
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
describe('installMenuRepositionFix', () => {
|
||||
it('при появлении меню стабилизирует позицию и шлёт один resize', async () => {
|
||||
teardown = installMenuRepositionFix();
|
||||
document.body.appendChild(makeMenu());
|
||||
await tick();
|
||||
flushFrames(6);
|
||||
expect(resizeSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('на посторонний узел не реагирует', async () => {
|
||||
teardown = installMenuRepositionFix();
|
||||
const other = document.createElement('div');
|
||||
other.className = 'some-card';
|
||||
document.body.appendChild(other);
|
||||
await tick();
|
||||
flushFrames(6);
|
||||
expect(resizeSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('идемпотентна: двойная установка не даёт двойной resize', async () => {
|
||||
teardown = installMenuRepositionFix();
|
||||
installMenuRepositionFix(); // второй вызов — noop
|
||||
document.body.appendChild(makeMenu());
|
||||
await tick();
|
||||
flushFrames(6);
|
||||
expect(resizeSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('предохранитель: если геометрия не устаканилась — не виснет и не шлёт resize', async () => {
|
||||
rectWidth = 0; // контент «нулевой» → условие стабилизации не выполняется
|
||||
teardown = installMenuRepositionFix();
|
||||
document.body.appendChild(makeMenu());
|
||||
await tick();
|
||||
flushFrames(120); // больше предохранителя (90 кадров)
|
||||
expect(resizeSpy).not.toHaveBeenCalled();
|
||||
expect(rafQueue.length).toBe(0); // цикл остановился, не зациклился
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Прогнать тест — убедиться, что падает**
|
||||
|
||||
Run (из `app/`): `npm run test:vue -- menuRepositionFix`
|
||||
Expected: FAIL — `installMenuRepositionFix` не экспортируется из `menuRepositionFix.ts`.
|
||||
|
||||
- [ ] **Step 3: Реализовать установщик**
|
||||
|
||||
Переписать `app/resources/js/utils/menuRepositionFix.ts` так (шапку-комментарий сохранить, ниже — тело):
|
||||
|
||||
```ts
|
||||
// Ядро: дождаться, пока геометрия последнего меню устаканится, и один раз
|
||||
// послать resize — Vuetify пересчитает позицию по стабильной геометрии.
|
||||
function scheduleStabilize(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
let prevLeft = Number.NaN;
|
||||
let stableFrames = 0;
|
||||
let totalFrames = 0;
|
||||
|
||||
const tick = (): void => {
|
||||
const menus = document.querySelectorAll<HTMLElement>('.v-overlay.v-menu .v-overlay__content');
|
||||
const el = menus[menus.length - 1];
|
||||
|
||||
if (el && el.getBoundingClientRect().width > 0) {
|
||||
const left = Math.round(el.getBoundingClientRect().left);
|
||||
stableFrames = left === prevLeft ? stableFrames + 1 : 0;
|
||||
prevLeft = left;
|
||||
if (stableFrames >= 3) {
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (++totalFrames < 90) requestAnimationFrame(tick);
|
||||
};
|
||||
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Ручной per-instance обход. Заменён глобальным
|
||||
* installMenuRepositionFix(). Оставлен временно до снятия ручных пометок.
|
||||
*/
|
||||
export function repositionMenuAfterOpen(open: boolean): void {
|
||||
if (!open) return;
|
||||
scheduleStabilize();
|
||||
}
|
||||
|
||||
let installed = false;
|
||||
|
||||
/**
|
||||
* Глобально включает обход бага позиционирования меню Vuetify: один
|
||||
* MutationObserver ловит появление любого `.v-overlay.v-menu` и запускает
|
||||
* стабилизацию. Идемпотентна (повторный вызов — noop). SSR-safe.
|
||||
* Возвращает teardown (для тестов / явной остановки).
|
||||
*/
|
||||
export function installMenuRepositionFix(): () => void {
|
||||
const noop = (): void => {};
|
||||
if (installed) return noop;
|
||||
if (
|
||||
typeof window === 'undefined' ||
|
||||
typeof document === 'undefined' ||
|
||||
typeof MutationObserver === 'undefined' ||
|
||||
!document.body
|
||||
) {
|
||||
return noop;
|
||||
}
|
||||
|
||||
installed = true;
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
for (const m of mutations) {
|
||||
for (const node of m.addedNodes) {
|
||||
if (!(node instanceof HTMLElement)) continue;
|
||||
if (node.matches('.v-overlay.v-menu') || node.querySelector('.v-overlay.v-menu')) {
|
||||
scheduleStabilize();
|
||||
return; // одного запуска на пачку мутаций достаточно
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
installed = false;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
> Примечание: блок JSDoc-шапки в начале файла (описание бага) сохранить как есть — он остаётся актуальным.
|
||||
|
||||
- [ ] **Step 4: Прогнать тест — зелёный**
|
||||
|
||||
Run (из `app/`): `npm run test:vue -- menuRepositionFix`
|
||||
Expected: PASS — все 4 кейса.
|
||||
|
||||
- [ ] **Step 5: Коммит**
|
||||
|
||||
```bash
|
||||
git add app/resources/js/utils/menuRepositionFix.ts app/tests/Frontend/menuRepositionFix.spec.ts
|
||||
git commit -m "feat(fe): глобальный installMenuRepositionFix + тест механизма"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Подключить установщик в точке запуска
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/resources/js/app.ts`
|
||||
|
||||
- [ ] **Step 1: Добавить импорт и вызов после mount**
|
||||
|
||||
В `app/resources/js/app.ts` добавить импорт рядом с остальными:
|
||||
|
||||
```ts
|
||||
import { installMenuRepositionFix } from './utils/menuRepositionFix';
|
||||
```
|
||||
|
||||
И сразу после `app.mount('#app');` добавить строку:
|
||||
|
||||
```ts
|
||||
app.mount('#app');
|
||||
installMenuRepositionFix();
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Проверка типов и сборки**
|
||||
|
||||
Run (из `app/`): `npm run type-check`
|
||||
Expected: PASS (0 ошибок).
|
||||
|
||||
- [ ] **Step 3: Коммит**
|
||||
|
||||
```bash
|
||||
git add app/resources/js/app.ts
|
||||
git commit -m "feat(fe): подключить installMenuRepositionFix при запуске SPA"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Снять ручные пометки в 4 окнах
|
||||
|
||||
Глобальный обход уже активен (Task 2). Ручные `@update:menu` / `@update:model-value="repositionMenuAfterOpen"` и их импорты теперь лишние — снимаем. Точные строки могли сдвинуться; ориентир — по атрибуту и импорту.
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/resources/js/views/projects/NewProjectDialog.vue`
|
||||
- Modify: `app/resources/js/components/projects/RegionsBulkDialog.vue`
|
||||
- Modify: `app/resources/js/components/projects/ProjectDetailsDrawer.vue`
|
||||
- Modify: `app/resources/js/components/layout/AppTopbar.vue`
|
||||
|
||||
- [ ] **Step 1: Найти все вхождения**
|
||||
|
||||
Run (из корня репо): `grep -rn "repositionMenuAfterOpen" app/resources/js`
|
||||
Expected: вхождения только в 4 файлах выше (по 1 импорту + использования: NewProjectDialog ×1, RegionsBulkDialog ×2, ProjectDetailsDrawer ×1, AppTopbar ×2).
|
||||
|
||||
- [ ] **Step 2: Удалить строки-атрибуты и импорты**
|
||||
|
||||
В каждом из 4 файлов:
|
||||
1. Удалить строку(и) атрибута на компоненте: `@update:menu="repositionMenuAfterOpen"` (NewProjectDialog, RegionsBulkDialog ×2, ProjectDetailsDrawer) либо `@update:model-value="repositionMenuAfterOpen"` (AppTopbar ×2).
|
||||
2. Удалить строку импорта: `import { repositionMenuAfterOpen } from '../../utils/menuRepositionFix';` (путь относительный — как в файле).
|
||||
|
||||
Ничего другого в этих строках не трогать (атрибут на отдельной строке — удаляется целиком).
|
||||
|
||||
- [ ] **Step 3: Убедиться, что ссылок не осталось**
|
||||
|
||||
Run (из корня репо): `grep -rn "repositionMenuAfterOpen" app/resources/js`
|
||||
Expected: вхождения ТОЛЬКО в `app/resources/js/utils/menuRepositionFix.ts` (определение `@deprecated`-функции). В `.vue`-файлах — ничего.
|
||||
|
||||
- [ ] **Step 4: Тесты затронутых окон + типы**
|
||||
|
||||
Run (из `app/`): `npm run test:vue -- NewDealDialog RegionsBulkDialog ProjectDetailsDrawer DealsFilters TenantsFilters`
|
||||
Expected: PASS (затронутые окна рендерятся, обработчик отсутствует — не ломает).
|
||||
Run (из `app/`): `npm run type-check`
|
||||
Expected: PASS — нет «неиспользуемый импорт».
|
||||
|
||||
- [ ] **Step 5: Коммит**
|
||||
|
||||
```bash
|
||||
git add app/resources/js/views/projects/NewProjectDialog.vue app/resources/js/components/projects/RegionsBulkDialog.vue app/resources/js/components/projects/ProjectDetailsDrawer.vue app/resources/js/components/layout/AppTopbar.vue
|
||||
git commit -m "refactor(fe): снять ручные обходы меню - заменены глобальным установщиком"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Удалить мёртвый экспорт `repositionMenuAfterOpen`
|
||||
|
||||
После Task 3 функция нигде не используется. Удаляем экспорт — ядро остаётся внутренним, наружу торчит только `installMenuRepositionFix`.
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/resources/js/utils/menuRepositionFix.ts`
|
||||
|
||||
- [ ] **Step 1: Удалить `@deprecated`-функцию**
|
||||
|
||||
В `app/resources/js/utils/menuRepositionFix.ts` удалить весь блок:
|
||||
|
||||
```ts
|
||||
/**
|
||||
* @deprecated Ручной per-instance обход. Заменён глобальным
|
||||
* installMenuRepositionFix(). Оставлен временно до снятия ручных пометок.
|
||||
*/
|
||||
export function repositionMenuAfterOpen(open: boolean): void {
|
||||
if (!open) return;
|
||||
scheduleStabilize();
|
||||
}
|
||||
```
|
||||
|
||||
`scheduleStabilize` и `installMenuRepositionFix` остаются. (`scheduleStabilize` остаётся используемым из `installMenuRepositionFix`.)
|
||||
|
||||
- [ ] **Step 2: Полная верификация**
|
||||
|
||||
Run (из `app/`): `npm run test:vue -- menuRepositionFix`
|
||||
Expected: PASS (4 кейса).
|
||||
Run (из `app/`): `npm run type-check`
|
||||
Expected: PASS — нет неиспользуемого `scheduleStabilize`/мёртвого кода.
|
||||
Run (из `app/`): `npm run build`
|
||||
Expected: сборка успешна.
|
||||
|
||||
- [ ] **Step 3: Коммит**
|
||||
|
||||
```bash
|
||||
git add app/resources/js/utils/menuRepositionFix.ts
|
||||
git commit -m "refactor(fe): убрать мёртвый repositionMenuAfterOpen - ядро внутреннее"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Финальная проверка (после всех задач)
|
||||
|
||||
- [ ] Run (из `app/`): `npm run test:vue` — весь фронт-набор зелёный.
|
||||
- [ ] Run (из `app/`): `npm run type-check` — 0 ошибок.
|
||||
- [ ] Run (из `app/`): `npm run build` — сборка ок.
|
||||
- [ ] Глазами (после выката на прод по «выкатывай»): открыть «Тип лица» в визарде нового проекта, списки в «Новой сделке» и в диалоге импорта статусов — меню встают на место (особенно под Windows / reduced-motion).
|
||||
|
||||
---
|
||||
|
||||
## Self-review (выполнено автором плана)
|
||||
|
||||
- **Покрытие спеки:** §4.1 ядро+установщик → Task 1; §4.2 точка запуска → Task 2; §4.3 уборка пометок → Task 3 + Task 4 (удаление экспорта); §6 тесты → Task 1 (4 кейса) + финальная проверка. Гэпов нет.
|
||||
- **Плейсхолдеры:** нет TBD/TODO; весь код приведён целиком.
|
||||
- **Согласованность имён:** `scheduleStabilize`, `installMenuRepositionFix`, `repositionMenuAfterOpen` — употреблены одинаково во всех задачах; экспорт `repositionMenuAfterOpen` живёт Task 1→3, удаляется в Task 4 (по тексту согласовано).
|
||||
- **Зелёные коммиты:** старый экспорт держится до снятия всех ссылок (Task 3), только потом удаляется (Task 4) — сборка не ломается между коммитами.
|
||||
Reference in New Issue
Block a user