diff --git a/web/frontend/src/__tests__/kin-ui-025-fixes.test.ts b/web/frontend/src/__tests__/kin-ui-025-fixes.test.ts deleted file mode 100644 index 348e7ac..0000000 --- a/web/frontend/src/__tests__/kin-ui-025-fixes.test.ts +++ /dev/null @@ -1,297 +0,0 @@ -/** - * KIN-UI-025: Тесты трёх исправлений ревьюера - * - * Fix 1: Click-outside overlay — dropdown '+ New ▾' закрывается при клике вне меню - * Fix 2: i18n — ключ dashboard.search_placeholder присутствует в обоих локалях без second arg - * Fix 3: Stats-bar — статусы revising/cancelled/decomposed отображаются в счётчиках - */ - -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { mount, flushPromises } from '@vue/test-utils' -import { createRouter, createMemoryHistory } from 'vue-router' -import * as fs from 'node:fs' -import * as path from 'node:path' -import enJson from '../locales/en.json' -import ruJson from '../locales/ru.json' -import ProjectView from '../views/ProjectView.vue' -import { i18n } from '../i18n' - -// ============================================================================ -// Fix 2: i18n — dashboard.search_placeholder -// ============================================================================ - -describe('KIN-UI-025 Fix 2: dashboard.search_placeholder в en.json', () => { - it('ключ dashboard.search_placeholder присутствует в en.json', () => { - expect((enJson.dashboard as Record).search_placeholder).toBeDefined() - }) - - it('значение dashboard.search_placeholder в en.json корректно', () => { - expect((enJson.dashboard as Record).search_placeholder).toBe('Search projects...') - }) -}) - -describe('KIN-UI-025 Fix 2: dashboard.search_placeholder в ru.json', () => { - it('ключ dashboard.search_placeholder присутствует в ru.json', () => { - expect((ruJson.dashboard as Record).search_placeholder).toBeDefined() - }) - - it('значение dashboard.search_placeholder в ru.json корректно', () => { - expect((ruJson.dashboard as Record).search_placeholder).toBe('Поиск проектов...') - }) -}) - -describe('KIN-UI-025 Fix 2: Dashboard.vue использует t() без второго аргумента', () => { - it('вызов t("dashboard.search_placeholder") без второго аргумента (нет plural-хака)', () => { - const vuePath = path.resolve(__dirname, '../views/Dashboard.vue') - const source = fs.readFileSync(vuePath, 'utf-8') - // Должен быть вызов без второго аргумента - expect(source).toContain("t('dashboard.search_placeholder')") - // Не должно быть второго аргумента типа строки (старый plural-хак) - expect(source).not.toContain("t('dashboard.search_placeholder', '") - expect(source).not.toContain('t("dashboard.search_placeholder", "') - }) -}) - -// ============================================================================ -// Fix 1: Click-outside overlay в Dashboard.vue -// ============================================================================ - -describe('KIN-UI-025 Fix 1: Click-outside overlay в Dashboard.vue', () => { - it('шаблон содержит overlay div с fixed inset-0 и z-[5]', () => { - const vuePath = path.resolve(__dirname, '../views/Dashboard.vue') - const source = fs.readFileSync(vuePath, 'utf-8') - expect(source).toContain('fixed inset-0 z-[5]') - }) - - it('overlay показывается только когда showNewMenu === true (v-if)', () => { - const vuePath = path.resolve(__dirname, '../views/Dashboard.vue') - const source = fs.readFileSync(vuePath, 'utf-8') - // Overlay div должен быть внутри v-if="showNewMenu" - const overlayLineMatch = source.match(/v-if="showNewMenu"[^>]*fixed inset-0/) - expect(overlayLineMatch).not.toBeNull() - }) - - it('overlay закрывает меню при клике: @click="showNewMenu = false"', () => { - const vuePath = path.resolve(__dirname, '../views/Dashboard.vue') - const source = fs.readFileSync(vuePath, 'utf-8') - // Overlay должен иметь обработчик закрытия - const lines = source.split('\n') - const overlayLine = lines.find(l => l.includes('fixed inset-0 z-[5]')) - expect(overlayLine).toBeDefined() - expect(overlayLine).toContain('showNewMenu = false') - }) - - it('dropdown имеет z-10 — выше overlay z-[5]', () => { - const vuePath = path.resolve(__dirname, '../views/Dashboard.vue') - const source = fs.readFileSync(vuePath, 'utf-8') - // Меню должно иметь z-10 - expect(source).toContain('z-10') - // Overlay должен иметь z-[5] (ниже меню) - expect(source).toContain('z-[5]') - }) -}) - -// ============================================================================ -// Fix 3: Stats-bar — revising/cancelled/decomposed в ProjectView -// ============================================================================ - -vi.mock('../api', async (importOriginal) => { - const actual = await importOriginal() - return { - ...actual, - api: { - project: vi.fn(), - projects: vi.fn(), - getPhases: vi.fn(), - environments: vi.fn(), - projectLinks: vi.fn(), - patchProject: vi.fn(), - syncObsidian: vi.fn(), - }, - } -}) - -import { api } from '../api' - -const localStorageMock = (() => { - let store: Record = {} - return { - getItem: (k: string) => store[k] ?? null, - setItem: (k: string, v: string) => { store[k] = v }, - removeItem: (k: string) => { delete store[k] }, - clear: () => { store = {} }, - } -})() -Object.defineProperty(globalThis, 'localStorage', { value: localStorageMock, configurable: true }) - -function makeTask(id: string, status: string) { - return { - id, - project_id: 'proj-stats', - title: `Task ${id}`, - status, - priority: 5, - assigned_role: null, - parent_task_id: null, - brief: null, - spec: null, - execution_mode: null, - blocked_reason: null, - dangerously_skipped: null, - category: null, - acceptance_criteria: null, - feedback: null, - created_at: '2024-01-01T00:00:00', - updated_at: '2024-01-01T00:00:00', - completed_at: null, - } -} - -const STATS_PROJECT = { - id: 'proj-stats', - name: 'Stats Test Project', - path: '/projects/stats', - status: 'active', - priority: 5, - tech_stack: ['python'], - execution_mode: 'review', - autocommit_enabled: 0, - auto_test_enabled: 0, - worktrees_enabled: 0, - obsidian_vault_path: '', - deploy_command: '', - test_command: '', - deploy_host: '', - deploy_path: '', - deploy_runtime: '', - deploy_restart_cmd: '', - created_at: '2024-01-01', - total_tasks: 6, - done_tasks: 1, - active_tasks: 1, - blocked_tasks: 0, - review_tasks: 0, - project_type: 'development', - ssh_host: '', - ssh_user: '', - ssh_key_path: '', - ssh_proxy_jump: '', - description: null, - tasks: [ - makeTask('T1', 'done'), - makeTask('T2', 'in_progress'), - makeTask('T3', 'revising'), - makeTask('T4', 'cancelled'), - makeTask('T5', 'decomposed'), - makeTask('T6', 'pending'), - ], - modules: [], - decisions: [], -} - -function makeRouter() { - return createRouter({ - history: createMemoryHistory(), - routes: [{ path: '/project/:id', component: ProjectView, props: true }], - }) -} - -beforeEach(() => { - localStorageMock.clear() - vi.clearAllMocks() - vi.mocked(api.project).mockResolvedValue(STATS_PROJECT as any) - vi.mocked(api.projects).mockResolvedValue([]) - vi.mocked(api.getPhases).mockResolvedValue([]) - vi.mocked(api.environments).mockResolvedValue([]) - vi.mocked(api.projectLinks).mockResolvedValue([]) - vi.mocked(api.patchProject).mockResolvedValue(STATS_PROJECT as any) - i18n.global.locale.value = 'en' as any -}) - -describe('KIN-UI-025 Fix 3: Stats-бар — revising/cancelled/decomposed', () => { - it('taskStats computed содержит поле revising', async () => { - const vuePath = path.resolve(__dirname, '../views/ProjectView.vue') - const source = fs.readFileSync(vuePath, 'utf-8') - // Проверяем что в taskStats есть фильтрация по revising - expect(source).toContain("t.status === 'revising'") - }) - - it('taskStats computed содержит поле cancelled', async () => { - const vuePath = path.resolve(__dirname, '../views/ProjectView.vue') - const source = fs.readFileSync(vuePath, 'utf-8') - expect(source).toContain("t.status === 'cancelled'") - }) - - it('taskStats computed содержит поле decomposed', async () => { - const vuePath = path.resolve(__dirname, '../views/ProjectView.vue') - const source = fs.readFileSync(vuePath, 'utf-8') - expect(source).toContain("t.status === 'decomposed'") - }) - - it('шаблон отображает span для revising со стилем text-orange-400', () => { - const vuePath = path.resolve(__dirname, '../views/ProjectView.vue') - const source = fs.readFileSync(vuePath, 'utf-8') - const lines = source.split('\n') - const revisingSpan = lines.find(l => l.includes('taskStats.revising') && l.includes('text-orange-400')) - expect(revisingSpan).toBeDefined() - }) - - it('шаблон отображает span для cancelled', () => { - const vuePath = path.resolve(__dirname, '../views/ProjectView.vue') - const source = fs.readFileSync(vuePath, 'utf-8') - expect(source).toContain('taskStats.cancelled') - }) - - it('шаблон отображает span для decomposed', () => { - const vuePath = path.resolve(__dirname, '../views/ProjectView.vue') - const source = fs.readFileSync(vuePath, 'utf-8') - expect(source).toContain('taskStats.decomposed') - }) - - it('stats-бар рендерит "revising" для задачи со статусом revising', async () => { - const router = makeRouter() - await router.push('/project/proj-stats') - const wrapper = mount(ProjectView, { - props: { id: 'proj-stats' }, - global: { plugins: [router] }, - }) - await flushPromises() - - const statsText = wrapper.html() - expect(statsText).toContain('revising') - }) - - it('stats-бар рендерит "cancelled" для задачи со статусом cancelled', async () => { - const router = makeRouter() - await router.push('/project/proj-stats') - const wrapper = mount(ProjectView, { - props: { id: 'proj-stats' }, - global: { plugins: [router] }, - }) - await flushPromises() - - const statsText = wrapper.html() - expect(statsText).toContain('cancelled') - }) - - it('stats-бар рендерит "decomposed" для задачи со статусом decomposed', async () => { - const router = makeRouter() - await router.push('/project/proj-stats') - const wrapper = mount(ProjectView, { - props: { id: 'proj-stats' }, - global: { plugins: [router] }, - }) - await flushPromises() - - const statsText = wrapper.html() - expect(statsText).toContain('decomposed') - }) - - it('сумма всех статусов равна total (6 задач)', () => { - // Статическая проверка: taskStats возвращает revising+cancelled+decomposed в sum - const vuePath = path.resolve(__dirname, '../views/ProjectView.vue') - const source = fs.readFileSync(vuePath, 'utf-8') - // return объект должен включать все 8 статусов + total + pct - expect(source).toContain('revising, cancelled, decomposed') - }) -}) diff --git a/web/frontend/src/locales/en.json b/web/frontend/src/locales/en.json index bc034c1..ce4e34d 100644 --- a/web/frontend/src/locales/en.json +++ b/web/frontend/src/locales/en.json @@ -54,7 +54,6 @@ "proxy_jump_placeholder": "ProxyJump (optional, e.g. jumpt)", "path_required": "Path is required", "ssh_host_required": "SSH host is required for operations projects", - "search_placeholder": "Search projects...", "bootstrap_path_placeholder": "Project path (e.g. ~/projects/vdolipoperek)", "roles": { "business_analyst": { @@ -176,9 +175,7 @@ "acceptance_criteria_label": "Acceptance criteria", "acceptance_criteria_placeholder": "What should the output be? What result counts as success?", "create_followup": "🔗 Create Follow-up", - "generating_followup": "Generating...", - "review_required": "Review required:", - "banner_auto_mode": "🔓 Auto mode" + "generating_followup": "Generating..." }, "projectView": { "tasks_tab": "Tasks", diff --git a/web/frontend/src/locales/ru.json b/web/frontend/src/locales/ru.json index a4967a7..d85c638 100644 --- a/web/frontend/src/locales/ru.json +++ b/web/frontend/src/locales/ru.json @@ -54,7 +54,6 @@ "proxy_jump_placeholder": "ProxyJump (опционально, например jumpt)", "path_required": "Путь обязателен", "ssh_host_required": "SSH хост обязателен для операционных проектов", - "search_placeholder": "Поиск проектов...", "bootstrap_path_placeholder": "Путь к проекту (например ~/projects/vdolipoperek)", "roles": { "business_analyst": { @@ -176,9 +175,7 @@ "acceptance_criteria_label": "Критерии приёмки", "acceptance_criteria_placeholder": "Что должно быть на выходе? Какой результат считается успешным?", "create_followup": "🔗 Создать зависимости", - "generating_followup": "Создаём...", - "review_required": "Требует проверки:", - "banner_auto_mode": "🔓 Авто режим" + "generating_followup": "Создаём..." }, "projectView": { "tasks_tab": "Задачи", diff --git a/web/frontend/src/views/Dashboard.vue b/web/frontend/src/views/Dashboard.vue index 1966342..9fe26ad 100644 --- a/web/frontend/src/views/Dashboard.vue +++ b/web/frontend/src/views/Dashboard.vue @@ -205,7 +205,6 @@ async function createNewProject() {

{{ t('dashboard.cost_this_week') }}: ${{ totalCost.toFixed(2) }}

-
diff --git a/web/frontend/src/views/TaskDetail.vue b/web/frontend/src/views/TaskDetail.vue index 0743912..1fd783f 100644 --- a/web/frontend/src/views/TaskDetail.vue +++ b/web/frontend/src/views/TaskDetail.vue @@ -283,24 +283,6 @@ async function runPipeline() { const hasSteps = computed(() => (task.value?.pipeline_steps?.length ?? 0) > 0) const isRunning = computed(() => task.value?.status === 'in_progress') const isManualEscalation = computed(() => task.value?.brief?.task_type === 'manual_escalation') -const useVerticalPipeline = computed(() => (task.value?.pipeline_steps?.length ?? 0) > 5) - -const expandedSteps = ref>({}) -function toggleStepExpand(id: number) { - expandedSteps.value = { ...expandedSteps.value, [id]: !expandedSteps.value[id] } -} - -function verticalStepIcon(step: PipelineStep) { - if (step.success === true || step.success === 1) return '\u2713' - if (step.success === false || step.success === 0) return '\u2717' - return '\u25CF' -} - -function verticalStepIconColor(step: PipelineStep) { - if (step.success === true || step.success === 1) return 'text-green-400' - if (step.success === false || step.success === 0) return 'text-red-400' - return 'text-blue-400' -} const resolvingManually = ref(false) @@ -500,36 +482,13 @@ async function saveEdit() {
- -
- {{ t('taskDetail.review_required') }} - - - - -
-

{{ t('taskDetail.pipeline') }} {{ t('taskDetail.running') }}

- -
+
- - -
-
-
- {{ verticalStepIcon(step) }} - {{ roleIcons[step.agent_role] || '\u{1F916}' }} - {{ step.agent_role }} - {{ step.duration_seconds }}s - ${{ step.cost_usd?.toFixed(3) }} - {{ expandedSteps[step.id] ? '▲' : '▼' }} -
-
- - -
-
-