Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions frontend/e2e/project-analysis-failure.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ test('Workspace 분석 실패를 샘플 성공으로 대체하지 않는다', as
await expect(page.getByText('분석 실패', { exact: true }).first()).toBeVisible()
await expect(page.getByRole('heading', { name: /분석하지 못했습니다/ })).toBeVisible()
await expect(page.getByText('샘플 프로젝트로 자동 전환하지 않았습니다.')).toBeVisible()
await expect(page.getByRole('button', { name: /API 요청/ })).toBeDisabled()
await expect(page.getByRole('button', { name: /^3 Trace/ })).toBeDisabled()
const workflow = page.getByRole('navigation', { name: 'StackFlow 작업 단계' })
await expect(workflow).toHaveCount(1)
await expect(page.getByRole('list', { name: '분석 증거 단계' })).toHaveCount(0)
await expect(workflow.getByRole('button', { name: /프로젝트 구조.*분석 실패/ })).toHaveAttribute('aria-current', 'page')
await expect(workflow.getByRole('button', { name: /API 요청.*분석 후 사용/ })).toBeDisabled()
await expect(workflow.getByRole('button', { name: /Trace.*API 준비 필요/ })).toBeDisabled()
await expect(page.getByRole('button', { name: '다시 분석' }).first()).toBeVisible()
await expect(page.getByRole('button', { name: '데모 프로젝트 열기' })).toBeVisible()
await expect(page.getByText('StackFlow 샘플')).toHaveCount(0)
await expect(page.getByText('StackFlow 샘플', { exact: true })).toHaveCount(0)

await page.setViewportSize({ width: 390, height: 844 })
await expect.poll(() => page.evaluate(() =>
Expand Down
125 changes: 34 additions & 91 deletions frontend/src/App.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
.app-shell {
--topbar-height: 68px;
--workflow-nav-height: 56px;
--workbench-sticky-offset: calc(var(--topbar-height) + var(--workflow-nav-height));
--workbench-panel-offset: calc(var(--workbench-sticky-offset) + 37px);
min-height: 100vh;
color: var(--color-ink);
background: var(--color-canvas);
Expand Down Expand Up @@ -29,10 +33,10 @@ summary:focus-visible {
top: 0;
z-index: 30;
display: grid;
grid-template-columns: minmax(220px, 1fr) auto auto;
grid-template-columns: minmax(220px, 1fr) auto;
align-items: center;
gap: 24px;
min-height: 68px;
min-height: var(--topbar-height);
border-bottom: 1px solid var(--color-line);
background: rgba(255, 255, 255, 0.96);
padding: 10px 24px;
Expand Down Expand Up @@ -103,68 +107,13 @@ summary:focus-visible {
font: 650 0.78rem/1.3 var(--font-mono);
}

.evidence-progress {
display: flex;
align-items: center;
gap: 0;
margin: 0;
padding: 0;
list-style: none;
}

.evidence-progress li {
position: relative;
display: flex;
align-items: center;
gap: 6px;
color: #8995a4;
font-size: 0.72rem;
font-weight: 650;
white-space: nowrap;
}

.evidence-progress li:not(:last-child)::after {
width: 34px;
height: 1px;
margin: 0 9px;
background: var(--color-line-strong);
content: '';
}

.evidence-progress li.is-ready {
color: var(--color-spring);
}

.evidence-progress li.is-active {
color: var(--color-action);
}

.evidence-progress__icon {
display: grid;
place-items: center;
width: 24px;
height: 24px;
border: 1px solid var(--color-line-strong);
border-radius: 50%;
background: var(--color-surface);
}

.evidence-progress .is-ready .evidence-progress__icon {
border-color: rgba(31, 122, 85, 0.28);
background: var(--color-spring-soft);
}

.evidence-progress .is-active .evidence-progress__icon {
border-color: rgba(37, 99, 235, 0.34);
background: var(--color-action-soft);
}

.workflow-tabs {
position: sticky;
top: 68px;
top: var(--topbar-height);
z-index: 25;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
height: var(--workflow-nav-height);
border-bottom: 1px solid var(--color-line);
background: var(--color-surface);
padding: 0 24px;
Expand All @@ -176,11 +125,11 @@ summary:focus-visible {
grid-template-columns: 22px 18px minmax(0, 1fr);
align-items: center;
gap: 9px;
min-height: 62px;
height: 100%;
border: 0;
border-right: 1px solid var(--color-line);
background: transparent;
padding: 9px 18px;
padding: 6px 18px;
text-align: left;
cursor: pointer;
}
Expand Down Expand Up @@ -234,6 +183,7 @@ summary:focus-visible {

.workflow-tab strong {
font-size: 0.86rem;
line-height: 1.2;
}

.workflow-tab small {
Expand All @@ -242,14 +192,27 @@ summary:focus-visible {
color: var(--color-muted);
font-size: 0.69rem;
font-weight: 450;
line-height: 1.2;
text-overflow: ellipsis;
white-space: nowrap;
}

.workflow-tab--success small {
color: var(--color-spring);
}

.workflow-tab--warning small {
color: var(--color-warning);
}

.workflow-tab--error small {
color: var(--color-error);
}

.workspace {
display: grid;
grid-template-columns: 300px minmax(560px, 1fr) 330px;
min-height: calc(100vh - 131px);
min-height: calc(100vh - var(--workbench-sticky-offset));
}

.left-panel,
Expand All @@ -269,9 +232,9 @@ summary:focus-visible {
.control-rail,
.inspector-rail {
position: sticky;
top: 131px;
top: var(--workbench-sticky-offset);
align-self: start;
max-height: calc(100vh - 131px);
max-height: calc(100vh - var(--workbench-sticky-offset));
overflow: auto;
}

Expand All @@ -294,7 +257,7 @@ summary:focus-visible {
.panel-card--map,
.panel-card--api-flow,
.panel-card--graph {
min-height: calc(100vh - 168px);
min-height: calc(100vh - var(--workbench-panel-offset));
border: 1px solid var(--color-line);
border-radius: 8px;
background: var(--color-surface);
Expand Down Expand Up @@ -1473,26 +1436,13 @@ summary:focus-visible {
border-right: 1px solid var(--color-line);
}

.evidence-progress li:not(:last-child)::after {
width: 18px;
}
}

@media (max-width: 980px) {
.topbar {
grid-template-columns: minmax(0, 1fr) auto;
}

.evidence-progress {
grid-column: 1 / -1;
grid-row: 2;
justify-content: center;
}

.workflow-tabs {
top: 96px;
}

.workspace {
grid-template-columns: 1fr;
}
Expand Down Expand Up @@ -1545,16 +1495,6 @@ summary:focus-visible {
padding-top: 8px;
}

.evidence-progress {
justify-content: flex-start;
overflow-x: auto;
}

.evidence-progress li:not(:last-child)::after {
width: 12px;
margin: 0 5px;
}

.workflow-tabs {
position: sticky;
top: 0;
Expand All @@ -1564,21 +1504,24 @@ summary:focus-visible {
.workflow-tab {
grid-template-columns: auto auto;
justify-content: center;
min-height: 54px;
gap: 6px;
padding: 8px 5px;
text-align: center;
}

.workflow-tab__index,
.workflow-tab small {
.workflow-tab__index {
display: none;
}

.workflow-tab strong {
font-size: 0.75rem;
}

.workflow-tab small {
margin-top: 2px;
font-size: 0.6rem;
}

.graph-panel,
.control-card,
.inspector-workbench {
Expand Down
37 changes: 0 additions & 37 deletions frontend/src/components/EvidenceProgress.tsx

This file was deleted.

44 changes: 43 additions & 1 deletion frontend/src/components/WorkflowTabs.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { render, screen } from '@testing-library/react'
import { render, screen, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, expect, it, vi } from 'vitest'
import { WorkflowTabs } from './WorkflowTabs'
Expand All @@ -9,7 +9,9 @@ describe('WorkflowTabs', () => {
render(
<WorkflowTabs
activeView="project"
projectStatus="SUCCESS"
hasDetectedApis
requestReady
traceAvailable={false}
externalProject
onChange={onChange}
Expand All @@ -18,6 +20,46 @@ describe('WorkflowTabs', () => {

await userEvent.click(screen.getByRole('button', { name: /API 요청/ }))
expect(onChange).toHaveBeenCalledWith('api')
expect(screen.getByRole('button', { name: /프로젝트 구조/ })).toHaveAttribute('aria-current', 'page')
expect(screen.getByText('분석 완료')).toBeInTheDocument()
expect(screen.getByText('요청 가능')).toBeInTheDocument()
expect(screen.getByText('요청 후 확인')).toBeInTheDocument()
expect(screen.getByRole('button', { name: /Trace/ })).toBeDisabled()
})

it('distinguishes analysis-only APIs from unavailable workflow stages', () => {
const { container, rerender } = render(
<WorkflowTabs
activeView="api"
projectStatus="SUCCESS"
hasDetectedApis
requestReady={false}
traceAvailable={false}
externalProject={false}
onChange={vi.fn()}
/>,
)

expect(within(container).getByText('정적 분석만')).toBeInTheDocument()
expect(within(container).getByRole('button', { name: /API 요청/ })).toBeEnabled()
expect(within(container).getByRole('button', { name: /Trace/ })).toBeEnabled()

rerender(
<WorkflowTabs
activeView="project"
projectStatus="FAILED"
hasDetectedApis={false}
requestReady={false}
traceAvailable={false}
externalProject
onChange={vi.fn()}
/>,
)

expect(within(container).getByText('분석 실패')).toBeInTheDocument()
expect(within(container).getByText('분석 후 사용')).toBeInTheDocument()
expect(within(container).getByText('API 준비 필요')).toBeInTheDocument()
expect(within(container).getByRole('button', { name: /API 요청/ })).toBeDisabled()
expect(within(container).getByRole('button', { name: /Trace/ })).toBeDisabled()
})
})
Loading
Loading