UI설계

진행률 막대 하나로는 설명되지 않는 작업: 파이프라인을 그대로 드러낸 UI 재설계

2026. 08. 28

변환에 수십 초가 걸리는데 화면에는 퍼센트 하나만 표시되어, 사용자가 멈춘 것으로 판단하고 이탈했습니다. 백엔드를 4단계로 나눈 뒤 그 단계를 화면에 그대로 노출하고, 앱 전체를 재설계한 기록입니다.

프로젝트 배경

소속
개인 프로젝트
프로젝트
Notaformat · 손글씨 문서 변환기
기간
2025.11 최초 구현 ~ 2026.08 재설계
담당
기획·설계·구현 전 과정 단독

손으로 쓴 노트 사진을 올리면 수식·다이어그램·차트를 인식해 편집 가능한 형태로 복원하고 PDF로 조립하는 서비스입니다. 백엔드를 4단계 에이전트 파이프라인으로 재작성한 뒤, 그 구조를 반영해 프런트엔드 전체를 다시 설계했습니다.

UI설계반응형프런트엔드사용자경험디자인시스템

1. 배경: 기다리는 동안 아무것도 알 수 없는 화면

이 서비스의 변환은 페이지 수와 영역 수에 따라 수십 초가 걸립니다. 사진에서 글과 그림 영역을 찾고, 영역마다 종류를 판정하고, 판정에 맞춰 다시 그린 뒤 문서로 조립하는 과정을 거치기 때문입니다.

그 시간 동안 화면에 표시되는 정보는 퍼센트 하나였습니다. 62%라는 숫자만으로는 지금 무엇을 하고 있는지, 얼마나 더 걸릴지, 정상 동작 중인지를 판단할 수 없습니다.

수십 초 동안 변화가 보이지 않는 화면에서 사용자는 작업이 멈췄다고 판단합니다. 실제로는 정상 처리 중이어도 새로고침하거나 창을 닫으면 그때까지의 처리가 버려집니다. 변환 품질을 아무리 올려도 결과를 보기 전에 이탈하면 의미가 없습니다.

2. 문제 정의

2.1 보여줄 정보가 없던 것이 아니라 통로가 없었습니다

백엔드를 4단계 파이프라인으로 재작성하면서 각 단계의 진행 상황이 상태 객체에 남게 되었습니다. 어느 단계인지, 그 단계에서 몇 개를 처리했는지가 이미 서버에 존재했습니다.

문제는 그 정보가 프런트로 나올 경로가 없었다는 점입니다. 진행률 API는 퍼센트와 메시지만 반환하고 있었고, 화면은 그 퍼센트를 막대 하나로 그리고 있었습니다.

없는 정보를 만들어야 하는 상황이 아니라, 있는 정보를 드러내지 않고 있던 상황이었습니다.

2.2 앱 화면이 뷰포트에 고정되어 모바일에서 잘렸습니다

화면 8개가 모두 다음 규칙을 공유하고 있었습니다.

cssheight: 100vh;
overflow: hidden;

모바일 브라우저에서 100vh는 주소창이 없다고 가정한 높이입니다. 실제 보이는 영역보다 크므로 하단이 주소창 뒤로 들어가고, overflow: hidden이 걸려 있어 잘린 부분에 접근할 수단조차 없었습니다.

2.3 화면을 벗어날 방법이 없었습니다

변환 화면과 다운로드 화면에는 뒤로 가기나 홈으로 가는 수단이 없었습니다. 다운로드 화면의 "홈으로 돌아가기" 링크 하나가 전부였고, 변환 중에는 어떤 이탈 경로도 없었습니다.

3. 기술적 난점

이 앱은 라우터 없이 useState로 화면을 교체하는 구조입니다.

tsxtype Stage = "start" | "convert" | "done" | "login" | ...
const [step, setStep] = useState<Stage>("start");

구조가 단순해 화면 추가는 쉬우나, 두 가지 제약이 따라옵니다. 첫째, 화면마다 URL이 없으므로 특정 화면을 링크로 공유할 수 없습니다. 둘째, 브라우저 뒤로 가기가 동작하지 않으므로 이탈 경로를 화면 안에 직접 만들어야 합니다.

레이아웃 쪽에도 제약이 있었습니다. 화면 내부 요소가 대부분 position: absolute로 컨테이너에 고정되어 있어, 컨테이너의 높이 규칙을 바꾸면 내부 요소가 함께 움직입니다.

4. 설계 결정 및 근거

4.1 파이프라인 단계를 화면 구조로 그대로 옮겼습니다

진행률 API가 현재 단계와 세부 카운트를 함께 반환하도록 바꾸고, 화면은 그것을 4행 리스트로 그렸습니다.

① 텍스트 추출   ✓
② 영역 분류     ✓
③ 요소 재생성   ● 5/8
④ 문서 조립     ○

막대 하나를 네 줄로 늘린 것이 아니라, 백엔드 구조를 화면에 반영한 것입니다. 단계 이름과 순서가 서버의 파이프라인 노드와 일대일로 대응하므로, 백엔드에 단계가 추가되면 화면에도 같은 방식으로 추가됩니다.

완료 단계는 초록 체크, 진행 단계는 파란 원과 세부 카운트로 구분했습니다. 이전에는 완료와 진행이 모두 파란색이라 구분이 약했습니다.

4.2 레이아웃 규칙을 전역 한 곳으로 모았습니다

화면 8개의 모듈 CSS를 각각 수정하지 않고, 앱 화면을 감싸는 셸을 만들어 전역에서 덮었습니다.

규칙이 7곳에 흩어져 있으면 다음에 화면을 추가할 때 같은 문제가 재발합니다. 한 곳에서 강제하면 이후 추가되는 화면도 자동으로 같은 규칙을 따릅니다.

조건동작
데스크톱회색 배경 위 카드, 상하 여백 확보
세로 800px 이하카드 최소 높이 포기, 뷰포트에 맞춤
가로 480px 이하카드 연출 제거, 화면 전체 사용

100vh100dvh로 교체했습니다. dvh는 주소창을 제외한 실제 표시 영역을 반영합니다. 미지원 브라우저를 위해 vh를 먼저 선언하고 dvh로 덮는 순서를 사용했습니다.

4.3 이탈 경로를 화면마다 같은 자리에 두었습니다

공통 내비게이션 컴포넌트를 만들어 좌측 뒤로 가기, 우측 홈 버튼을 고정 위치에 배치했습니다. 라우터가 없어 브라우저 뒤로 가기가 동작하지 않으므로, 화면 안에 명시적인 경로가 필요했습니다.

로고도 좌측 상단으로 옮기고 홈 이동을 연결했습니다. 서비스명이 브라우저 탭 제목에만 있고 화면에는 없던 상태였습니다.

4.4 랜딩 페이지를 앞에 두었습니다

기존에는 접속하자마자 앱 화면이 나왔습니다. 처음 들어온 사람이 이 서비스가 무엇을 하는지 알 방법이 없었습니다.

랜딩을 앞에 두되 로그인 없이 바로 변환할 수 있도록 했습니다. 백엔드가 이미 비로그인 변환을 허용하고 있었으므로 서버 변경 없이 적용 가능했습니다. 로그인은 이력을 영구 보관하려는 시점에만 요구합니다.

랜딩만 풀와이드로 두고 앱 화면은 393px 고정을 유지했습니다. 두 화면의 목적이 다르므로 폭 규칙을 통일할 이유가 없습니다.

5. 실행 과정에서 확인한 것

5.1 회귀를 스크린샷으로 잡았습니다

레이아웃 변경 후 기기 8종에서 캡처해 확인했습니다. 이 과정에서 육안으로 확인하지 않았다면 놓쳤을 문제 두 건이 드러났습니다.

첫째, 1280×720 노트북에서 화면이 통째로 비었습니다. 카드 높이를 auto로 바꿨는데, 내부 요소가 대부분 position: absolute라 높이에 기여하지 않아 컨테이너가 0px로 접혔습니다. 명시적 높이로 되돌려 해결했습니다.

둘째, 액션시트가 화면 밖으로 밀렸습니다. 컨테이너에 overflow-y: auto를 준 것이 원인이었습니다. 헤더·내비바·시트가 모두 컨테이너에 absolute로 고정되어 있어, 스크롤이 열리면 함께 밀립니다. 시트를 열면 실측 57px이 밀려 하단이 잘렸습니다.

5.2 잘림의 원인이 화면 크기가 아니었습니다

액션시트가 잘리는 현상을 처음에는 작은 화면 문제로 보았으나, 캡처해 보니 iPhone SE부터 15 Pro Max까지 모든 기기에서 동일하게 잘렸습니다.

원인은 위치 지정 방식이었습니다.

cssbottom: 23%;   /* 퍼센트로 위치를 잡되 내용은 아래로 자란다 */

시트의 하단을 화면 높이의 23% 지점에 두었으나 내용이 아래로 자라는 구조라, 화면 크기와 무관하게 내비바에 가려졌습니다. 내비바 바로 위에 고정하고 최대 높이와 내부 스크롤을 지정해 해결했습니다.

증상만 보고 원인을 추정했다면 화면 크기 분기를 추가하는 방향으로 갔을 것입니다. 실제 측정이 방향을 바꿨습니다.

6. 결과

6.1 기기별 검증

기기세로가로 스크롤컨테이너 높이
iPhone SE667없음667
Galaxy S8740없음740
iPhone 12844없음844
iPhone 15 Pro Max932없음932
Pixel 7915없음915
iPad mini1024없음720
노트북 1280×720720없음688
데스크톱 1440×900900없음720

8종 모두 가로 스크롤이 없고 컨테이너 높이가 정상입니다.

6.2 화면 변경 요약

항목이전이후
진행 표시퍼센트 1개4단계 + 세부 카운트
첫 진입앱 화면 직행랜딩 → 로그인 없이 체험
이탈 경로다운로드 화면 링크 1개화면마다 뒤로·홈 고정
높이 기준100vh + overflow: hidden100dvh + 영역별 스크롤
서비스명 노출탭 제목만로고 컴포넌트 (앱·랜딩·푸터)

6.3 남은 것

실제 사용자 지표는 측정하지 않았습니다. 이탈률 개선을 목표로 한 작업이나 배포 전이므로 전후 비교 데이터가 없습니다. 현재 확인한 것은 기기별 레이아웃 정상 동작과 기능 회귀 부재까지입니다.

7. 결론 및 시사점

7.1 UI가 보여줄 것이 없으면 백엔드 구조를 의심할 수 있습니다

진행 상황을 4단계로 표시할 수 있게 된 것은 프런트엔드 작업의 결과가 아닙니다. 백엔드를 4단계로 나눴기 때문에 표시할 단위가 생긴 것입니다.

단일 함수였을 때는 아무리 UI를 고쳐도 퍼센트 하나 이상을 보여줄 수 없었습니다. 화면에 표시할 정보가 빈약하다면, 그것이 UI 문제가 아니라 내부 구조가 그 정보를 갖고 있지 않기 때문일 수 있습니다.

7.2 흩어진 규칙은 다음 화면에서 다시 어긋납니다

레이아웃 규칙을 화면 8개에 각각 넣지 않고 전역 셸 한 곳으로 모은 것이 이번 작업에서 가장 효과가 컸습니다.

개별 수정은 당장의 화면은 고치지만 다음에 추가되는 화면에는 적용되지 않습니다. 한 곳에서 강제하면 이후 코드가 자동으로 같은 규칙을 따릅니다. 같은 판단을 이미지 축소 최적화에서도 적용했고, 두 경우 모두 "선택적으로 적용되는 규칙은 시간이 지나면 적용되지 않는다"는 동일한 이유였습니다.

7.3 레이아웃 회귀는 눈으로 봐야 잡힙니다

타입 검사와 빌드는 통과했으나 화면이 비어 있는 상태를 잡아내지 못했습니다. 컨테이너 높이가 0px여도 문법 오류가 아니기 때문입니다.

기기별 캡처를 자동화해 두면 이런 회귀가 배포 전에 드러납니다. 이번 작업에서도 두 건을 이 방식으로 확인했으며, 그중 하나는 레이아웃을 수정하는 과정에서 새로 유입된 회귀였습니다.

7.4 증상의 분포가 원인을 가리킵니다

액션시트 잘림을 작은 화면 문제로 보았다면 화면 크기별 분기를 추가했을 것이고, 큰 화면에서는 여전히 잘렸을 것입니다.

모든 기기에서 동일하게 재현된다는 사실이 원인이 크기가 아님을 알려주었습니다. 증상이 특정 조건에서만 나타나는지 전부에서 나타나는지를 먼저 확인하면 잘못된 방향으로 가는 것을 막을 수 있습니다.