# Sprint 3E — Settings placeholder-tabs (D6/D7) Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Убрать из `SettingsView` 4 placeholder-вкладки («Проекты», «Команда», «Интеграции», «Тихие часы»), которые показывают «В разработке» — UI не должен обещать нереализованный функционал.

**Architecture:** Чистое удаление. `SettingsView` оставляет 4 рабочие вкладки (Профиль, Безопасность, API и Webhook, Уведомления). Компонент `PlaceholderTab.vue` удаляется целиком. Spec-тест приводится к 4-вкладочному состоянию + добавляется регрессионная проверка, что placeholder'ы пропали.

**Tech Stack:** Vue 3 (`<script setup>` + TypeScript), Vuetify 3, Vitest 4 + @vue/test-utils.

---

## Контекст и per-tab решение (audit D6/D7)

Аудит портала ([docs/superpowers/specs/2026-05-15-portal-audit-design.md](../specs/2026-05-15-portal-audit-design.md)):

- **D6** — «PlaceholderTab × 4 — реализовать или скрыть (decide per-tab)».
- **D7** — «SettingsView left-rail: 8 tab'ов, 4 заглушки — Hide-if-not-implemented».

**Per-tab решение — скрыть все 4** (реализация каждой = отдельный эпик, вне scope Sprint 3E):

| Вкладка | Решение | Обоснование |
|---|---|---|
| Проекты | скрыть | Полноценный `/projects` view уже есть — вкладка чистый дубль. |
| Команда | скрыть | Нет ни `/team`-маршрута, ни backend; реализация = отдельный L-эпик со schema-работой, не в графике спринтов. |
| Интеграции | скрыть | Telegram/1С/JivoSite/Yandex SSO — все внешне-блокированы (Б-1 и пр.). |
| Тихие часы | скрыть | `quiet_hours` отсутствует в `db/schema.sql`; ТЗ §17.8 спецификацию даёт, но колонок/backend нет — отдельный эпик. |

«Импорт»-вкладка из предложения D7 — это H8 (Sprint 4, миграция §6), **вне scope Sprint 3E**.

Скрытие не отменяет ТЗ-требования (Команда / Тихие часы §17.8) — вкладки вернутся при реальной реализации соответствующих модулей.

---

## File Structure

- Modify: `app/resources/js/views/SettingsView.vue` — убрать 4 placeholder-вкладки, `placeholderProps` computed, импорт и использование `PlaceholderTab`, неиспользуемый импорт `computed`; обновить docblock.
- Delete: `app/resources/js/views/settings/PlaceholderTab.vue` — компонент больше не используется.
- Test: `app/tests/Frontend/SettingsView.spec.ts` — 8 → 4 вкладки, убрать placeholder-тест, добавить регрессию.

**НЕ трогать:** `app/dev-indices.json` (авто-генерируемый временной DevIndex-фичей, уже `M` в git status — не стейджить, не коммитить); `SettingsView.story.vue` (ссылается только на `SettingsView`, не на `PlaceholderTab` — изменений не требует).

---

## Task 1: Скрыть 4 placeholder-вкладки в SettingsView

**Files:**

- Modify: `app/resources/js/views/SettingsView.vue`
- Delete: `app/resources/js/views/settings/PlaceholderTab.vue`
- Test: `app/tests/Frontend/SettingsView.spec.ts`

- [ ] **Step 1: Привести spec-тест к 4-вкладочному состоянию (failing test first)**

Заменить весь файл `app/tests/Frontend/SettingsView.spec.ts` на:

```ts
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import { createPinia } from 'pinia';
import { createVuetify } from 'vuetify';
import SettingsView from '../../resources/js/views/SettingsView.vue';

describe('SettingsView.vue', () => {
    const factory = () =>
        mount(SettingsView, {
            global: { plugins: [createPinia(), createVuetify()] },
        });

    it('монтируется и содержит заголовок «Настройки»', () => {
        const wrapper = factory();
        expect(wrapper.find('h1').text()).toBe('Настройки');
    });

    it('содержит ровно 4 nav-tabs (placeholder-вкладки убраны, audit D6/D7)', () => {
        const wrapper = factory();
        const items = wrapper.findAll('.tabs-rail .v-list-item');
        expect(items.length).toBe(4);
    });

    it('содержит все 4 названия рабочих вкладок', () => {
        const wrapper = factory();
        const text = wrapper.text();
        const labels = ['Профиль', 'Безопасность', 'API и Webhook', 'Уведомления'];
        labels.forEach((l) => expect(text).toContain(l));
    });

    it('не содержит placeholder-вкладок и текста «В разработке»', () => {
        const wrapper = factory();
        const railText = wrapper.find('.tabs-rail').text();
        ['Команда', 'Интеграции', 'Тихие часы'].forEach((l) => expect(railText).not.toContain(l));
        expect(wrapper.text()).not.toContain('В разработке');
    });

    it('по умолчанию показывает вкладку «Профиль»', () => {
        const wrapper = factory();
        const text = wrapper.text();
        // ProfileTab содержит поля Имя / Фамилия (split из «Полное имя» в audit D1) и Тайм-зона.
        expect(text).toContain('Имя');
        expect(text).toContain('Фамилия');
        expect(text).toContain('Тайм-зона');
    });

    it('переключение на «Уведомления» показывает матрицу 8×3', async () => {
        const wrapper = factory();
        const items = wrapper.findAll('.tabs-rail .v-list-item');
        const notifItem = items.find((i) => i.text().includes('Уведомления'));
        await notifItem!.trigger('click');
        await wrapper.vm.$nextTick();
        const text = wrapper.text();
        expect(text).toContain('События × каналы');
        // 8 типов событий из schema users.notification_preferences.
        ['Новый лид', 'Напоминание', 'Низкий баланс', 'Нулевой баланс', 'Анонсы и промо'].forEach((e) =>
            expect(text).toContain(e),
        );
    });

    it('переключение на «Безопасность» показывает 2FA и сессии', async () => {
        const wrapper = factory();
        const items = wrapper.findAll('.tabs-rail .v-list-item');
        const secItem = items.find((i) => i.text().includes('Безопасность'));
        await secItem!.trigger('click');
        await wrapper.vm.$nextTick();
        const text = wrapper.text();
        expect(text).toContain('Двухфакторная авторизация');
        expect(text).toContain('Активные сессии');
    });

    it('переключение на «API и Webhook» показывает API-ключ и signing secret', async () => {
        const wrapper = factory();
        const items = wrapper.findAll('.tabs-rail .v-list-item');
        const apiItem = items.find((i) => i.text().includes('API'));
        await apiItem!.trigger('click');
        await wrapper.vm.$nextTick();
        const text = wrapper.text();
        expect(text).toContain('API-ключ');
        expect(text).toContain('Signing secret');
        expect(text).toContain('HMAC');
    });
});
```

Изменения относительно текущего файла: тест «ровно 8 nav-tabs» → 4; «8 названий вкладок» → 4 рабочих; тест «placeholder-вкладки показывают „В разработке"» удалён, вместо него — регрессия «не содержит placeholder-вкладок».

- [ ] **Step 2: Прогнать тест — убедиться, что падает**

Run: `cd app && npm run test:vue -- --run SettingsView`
Expected: FAIL — текущий `SettingsView.vue` рендерит 8 вкладок, тесты «ровно 4 nav-tabs» и «не содержит placeholder-вкладок» красные.

- [ ] **Step 3: Удалить 4 placeholder-вкладки из `SettingsView.vue`**

Заменить блок `<script setup>` (строки 1–61) на:

```vue
<script setup lang="ts">
/**
 * Settings — настройки тенанта/пользователя. 4 рабочие вкладки.
 *
 * Источник дизайна: liderra_v8_handoff/concepts/v8_settings.html.
 * Полностью реализованы (с UI-разводкой): Профиль, Безопасность, API и Webhook,
 * Уведомления (матрица 8×3 по schema v8.7 §4 users.notification_preferences).
 *
 * Аудит D6/D7 (Sprint 3E, 2026-05-16): placeholder-вкладки Проекты/Команда/
 * Интеграции/Тихие часы убраны — UI не должен обещать «в разработке».
 * «Проекты» дублировали /projects; «Команда» и «Тихие часы» (ТЗ §17.8)
 * требуют schema+backend (отдельные эпики); «Интеграции» внешне-блокированы (Б-1).
 * Вкладки вернутся при реальной реализации соответствующих модулей.
 */
import { ref } from 'vue';
import ApiTab from './settings/ApiTab.vue';
import NotificationsTab from './settings/NotificationsTab.vue';
import ProfileTab from './settings/ProfileTab.vue';
import SecurityTab from './settings/SecurityTab.vue';

interface Tab {
    id: string;
    label: string;
    icon: string;
}

const tabs: Tab[] = [
    { id: 'profile', label: 'Профиль', icon: 'mdi-account-outline' },
    { id: 'security', label: 'Безопасность', icon: 'mdi-shield-lock-outline' },
    { id: 'api', label: 'API и Webhook', icon: 'mdi-api' },
    { id: 'notifications', label: 'Уведомления', icon: 'mdi-bell-outline' },
];

const activeTab = ref('profile');
</script>
```

В `<template>` заменить блок `<v-card variant="outlined" class="tab-pane pa-6">…</v-card>` (строки 89–99) на:

```vue
                <v-card variant="outlined" class="tab-pane pa-6">
                    <ProfileTab v-if="activeTab === 'profile'" />
                    <SecurityTab v-else-if="activeTab === 'security'" />
                    <ApiTab v-else-if="activeTab === 'api'" />
                    <NotificationsTab v-else-if="activeTab === 'notifications'" />
                </v-card>
```

`<style scoped>` — без изменений. Удаляются: импорт `PlaceholderTab`, импорт `computed` (становится неиспользуемым — остаётся только `ref`), `placeholderProps` computed, 4 строки placeholder-вкладок в `tabs`, `<PlaceholderTab>` в шаблоне.

- [ ] **Step 4: Удалить `PlaceholderTab.vue`**

Удалить файл `app/resources/js/views/settings/PlaceholderTab.vue` (`git rm`). Компонент больше нигде не импортируется (grep `PlaceholderTab` по `app/resources/js` → только `SettingsView.vue`, который мы уже почистили).

- [ ] **Step 5: Прогнать тест — убедиться, что зелёный**

Run: `cd app && npm run test:vue -- --run SettingsView`
Expected: PASS — все 8 тестов SettingsView зелёные.

- [ ] **Step 6: Проверить vue-tsc и ESLint**

Run: `cd app && npm run type-check` → 0 ошибок (важно: неиспользуемый импорт `computed` удалён, иначе vue-tsc/ESLint ругнётся).
Run: `cd app && npm run lint:vue` → 0 ошибок.

- [ ] **Step 7: Полный прогон Vitest (регрессия)**

Run: `cd app && npm run test:vue`
Expected: 0 failed. Базовый объём перед изменением — 100 файлов / 838 passed / 3 skipped; после Sprint 3E удалён 1 тест → ожидается 100 файлов / 837 passed / 3 skipped (точное число — из реального вывода, не экстраполировать).

- [ ] **Step 8: Commit**

```bash
git add app/resources/js/views/SettingsView.vue app/tests/Frontend/SettingsView.spec.ts
git rm app/resources/js/views/settings/PlaceholderTab.vue
git commit -m "feat(settings): D6/D7 — убрать placeholder-вкладки SettingsView"
```

**НЕ стейджить** `app/dev-indices.json` (авто-генерируемый, pre-existing `M`).

---

## Self-Review

- Spec coverage: D6 (4 placeholder-вкладки убраны) ✅; D7 (left-rail 8→4) ✅. «Импорт»-вкладка из D7 — H8/Sprint 4, явно вне scope.
- Placeholder scan: нет TODO/TBD; весь код приведён дословно.
- Type consistency: `tabs` остаётся `Tab[]`; `activeTab` — `ref('profile')`; `computed` удалён вместе с единственным потребителем `placeholderProps`.
