Skip to main content
← 블로그

화면이 감추고 있던 것들 — 필터, 오늘, 그리고 없는 버튼

VauDium·

숨긴 열의 필터는 어디에도 표시가 없었고, 오늘을 선택하면 선택 표시가 사라졌고, 삭제 버튼은 이유 없이 없었습니다. 상태는 멀쩡한데 화면이 말을 안 하던 하루의 기록입니다.

화면이 감추고 있던 것들 — 필터, 오늘, 그리고 없는 버튼

오늘 고친 것들을 늘어놓고 보니 공통점이 하나 있었습니다. 전부 상태는 멀쩡히 있는데 화면이 그걸 말하지 않던 문제였어요. 필터는 걸려 있는데 표시가 없었고, 날짜는 선택돼 있는데 선택으로 안 보였고, 버튼은 의도적으로 숨겨져 있는데 왜 숨었는지 알 길이 없었습니다.

이런 것들은 버그 리포트로 오지 않습니다. “왜 이것만 나오지?”, “안 보이는데?” 같은 짧은 말로 옵니다.

1. 숨긴 열의 필터는 어디에도 없었다

페칫 데스크톱의 표 뷰는 열마다 필터를 걸 수 있습니다. 상태, 프로젝트, 마감일, 담당자… 필터가 걸린 열은 헤더의 ⋯ 버튼이 눌린 채로 남아서, 지금 뭐가 걸려 있는지 한눈에 보이게 돼 있었죠.

문제는 열을 숨기면 그 표시도 같이 사라진다는 것이었습니다. 필터는 그대로 살아 있는데요.

마감일로 필터를 걸어 두고 마감일 열을 숨기면, 표는 조용히 일부만 보여줍니다. 화면 어디에도 이유가 없습니다. 열을 하나씩 다시 켜 보기 전에는 “왜 이것만 나오지?“가 풀리지 않아요. 눌린 ⋯ 는 보이는 열에만 뜰 수 있으니, 표시할 자리 자체가 없었던 겁니다.

그래서 현재 필터라는 드롭다운을 만들었습니다. 지금 걸려 있는 조건을 축별로 한 줄씩 늘어놓습니다. 상태는 이름으로, 날짜는 범위로, 포인트 컬러는 별로. 줄마다 ✕ 를 달아 그 축만 해제할 수 있고, 아래에 초기화가 있습니다. 숨긴 열의 필터도 여기서는 보입니다. 목록 자체가 열과 무관하게 조건을 훑으니까요.

저장해 둔 필터도 같은 문제를 갖고 있었습니다. 이름만 봐서는 뭘 거르는 필터인지 알 수 없어서, 확인하려면 일단 적용해 봐야 했어요. ⋯ 메뉴에 상세를 넣어서, 마우스만 올려도 옆에 조건이 펼쳐지게 했습니다. 같은 표기 로직을 두 곳이 나눠 쓰기 때문에 “현재 필터”와 “저장 필터 상세”는 언제나 같은 모양으로 읽힙니다.

곁가지로 표시 하나를 지웠습니다. 필터가 걸린 ⋯ 버튼 모서리에 파란 점이 붙어 있었는데, 눌린 배경과 겹쳐서 같은 사실을 두 번 말하고 있었고 다른 “적용됨” 표시들과도 문법이 달랐습니다. 점을 빼고 눌린 배경 하나로 통일했습니다. 이틀 전에 붙였던 표시를 이틀 만에 뗀 셈인데, 나란히 세워 보기 전에는 안 보이는 종류의 어긋남이었습니다.

컨테이너가 하는 두 가지 거짓말

만드는 동안 두 번 넘어졌는데, 둘 다 “컨테이너가 자기 크기에 대해 하는 말”과 관련이 있었습니다.

하나. 저장 필터 옆에 붙는 조건 패널을 메뉴 안쪽에 넣었더니 잘려 나갔습니다. 메뉴 상자에 세로 스크롤(overflow-y: auto)이 걸려 있었거든요. CSS 규칙상 한 축이 visible이 아니면 나머지 축도 visible로 남지 못합니다. 세로 스크롤을 켠 순간 가로도 잘리는 상자가 된 겁니다. 패널을 상자 으로 꺼내 형제로 두고 해결했습니다.

둘. 드롭다운이 화면 오른쪽 밖으로 튀어나갔습니다. 팝오버를 화면 안쪽으로 접어 주는 계산기에 폭과 높이를 손으로 적어 넘기는 구조였는데, 그 값이 조건 목록을 넣기 전의 옛날 치수(230×56)로 남아 있었습니다. 실제로는 260 폭에 320 높이가 됐으니, 계산기는 존재하지 않는 작은 상자를 기준으로 자리를 잡고 있었던 거죠. 선언한 치수와 실제 치수가 갈라지면 이런 버그는 조용히 삽니다.

2. 오늘을 선택하면, 선택이 사라졌다

주간 캘린더에서 오늘 날짜를 클릭하면 선택 배경이 아예 안 나왔습니다. 다른 날은 멀쩡한데 오늘만요.

코드에는 이렇게 적혀 있었습니다.

const dayBg = isSelected && !isToday ? PRIMARY50 : "transparent";

“선택됐고, 오늘이 아닐 때만.” 월간 그리드는 같은 자리에서 isSelected만 보고 있었으니, 주간 뷰만 조건이 하나 더 붙어 있었던 겁니다. 오늘과 선택은 서로 다른 축인데 한쪽이 다른 쪽을 지우고 있었어요. 가드를 떼는 것으로 끝났습니다.

이어서 같은 자리에서 더 큰 어긋남이 나왔습니다. 오른쪽 미니 달력에서는 **파란 채움 원이 “선택한 날”**이고, 왼쪽 주간·월간에서는 **같은 파란 채움 원이 “오늘”**이었습니다. 한 화면에 두 달력이 나란히 서 있는데, 같은 모양이 각자 다른 것을 가리키고 있었던 거죠.

기호는 하나의 뜻만 가져야 합니다. 파란 채움 원 = 언제나 오늘로 통일하고, 미니 달력의 “보고 있는 날”은 링(테두리 원)으로 내려보냈습니다. 선택은 여기서도 배경 계열, 오늘은 채움 계열 — 축이 갈렸습니다.

덤으로 주간 뷰의 선택 표시를 시간표 컬럼 전체로 이었습니다. 헤더만 옅게 칠해져 있으면 그 아래 시간표는 남의 열처럼 보였는데, 이제 위아래가 한 덩어리로 읽힙니다.

3. 조립이 두 벌이면 표류한다

커뮤니티에서 남의 할 일 템플릿을 가져가기 전에 미리보기로 열어 볼 수 있습니다. 그 미리보기가 실제 템플릿과 다르게 생겼다는 지적을 받았습니다.

세어 보니 통째로 빠진 섹션이 여섯 개였습니다. 대응 지침, 프로젝트, 의도(기대·목표·장애물·의미), 준비물, 회고, 첨부. 단계 목록은 있었지만 스텝 그래프(단계 사이 연결선)가 없어서 순서만 알 수 있고 갈래는 알 수 없었습니다.

원인은 단순했습니다. 상세 화면과 미리보기가 각자 따로 조립하고 있었어요. 상세에 섹션을 하나 더하면 미리보기에도 손으로 얹어야 하는데, 안 얹으면 조용히 벌어집니다. 모바일에는 이 문제를 겪고 세워 둔 규칙이 있었습니다 — “읽기 전용은 실제 상세와 같은 구조, 편집 손잡이만 제외”. 데스크톱 미리보기는 그 규칙 바깥에 있었던 거죠.

섹션 순서와 구성을 상세와 1:1로 맞춰 다시 짰습니다. 읽기 전용이라 두 가지만 달리 했습니다. 대응 지침과 준비물은 진입 행(›) 대신 그 자리에 목록을 펼칩니다 — 미리보기에서 모달을 또 겹치지 않으려고요. 그리고 리마인더와 일정 참여자는 뺐습니다. 참여자는 남의 주소록에 있는 사람들이라, 템플릿을 구경하러 온 사람에게 보일 이유가 없습니다.

한 줄만 다른 두 벌

스텝 그래프를 미리보기에 넣으려다 보니, 그 코드가 상세 두 화면(할 일 기록·템플릿)에 인라인으로 두 벌 있었습니다. 세 벌째를 쓸 수는 없어서 공용 컴포넌트로 뽑았는데, 그 과정에서 두 벌을 나란히 놓고 비교하게 됐습니다.

주석과 줄바꿈을 걷어내니 로직은 완전히 같았습니다. 딱 한 줄만 빼고요.

부모에서 자식 단계로 바로 들어오는 연결선을 그릴 때, 템플릿 쪽은 선을 그리고 기록 쪽은 안 그렸습니다. 어느 쪽이 옳은지는 코드만 봐서는 알 수 없었습니다. 둘 다 의도적으로 보였거든요.

그래서 통일하지 않았습니다. 플래그로 갈라 양쪽의 현재 동작을 그대로 보존하고, “여기 두 벌이 갈라져 있었다”는 사실을 주석과 커밋 메시지에 적었습니다. 리팩터링에서 제일 위험한 순간은 “이거 같은 거겠지” 하고 한쪽을 조용히 버리는 때입니다. 공용화의 목적은 코드를 줄이는 게 아니라 표류를 멈추는 것이라서, 차이를 남긴 채 합치는 게 차이를 지우고 합치는 것보다 낫습니다.

4. 없는 버튼에는 이유가 붙어야 한다

모바일에서 첨부 사진을 확대해서 보면 위쪽에 삭제 버튼이 있습니다. 그게 안 보인다는 지적을 받았습니다.

처음엔 색 문제인 줄 알았습니다. 실제로 색 문제도 있었어요. 닫기와 삭제 아이콘이 팔레트의 중간 회색 고정이라, 사진 위에 깔린 어두운 반투명 바에서 반쯤 잠겨 있었습니다. 같은 바에 있는 편집 아이콘만 흰색이라 밝기도 갈려 있었고요. 두 버튼에 색을 받을 수 있게 뚫고, 어두운 바 위 세 화면에 흰색을 넘겼습니다.

그런데 지적은 계속됐습니다. 찾아보니 삭제 버튼이 사라지는 조건은 하나뿐이었습니다 — 그 사진이 본문에 삽입돼 있을 때. 본문이 진실 소스라 첨부 쪽에서 직접 지울 수 없고, 서버도 그 삭제를 거부합니다. 그러니 버튼을 숨기는 건 맞습니다.

문제는 제가 그날 아침에 “본문에서 사용 중”이라는 안내 배지를 지웠다는 것이었습니다. 삭제 버튼이 이미 숨겨져 있으니 안내가 없어도 막히는 동작은 없다고 판단했거든요. 맞는 말이었지만 틀린 판단이었습니다. 그 배지는 버튼이 왜 없는지를 설명하는 유일한 문장이었습니다. 배지를 지운 순간, 의도적인 숨김이 원인 불명의 부재로 바뀌었습니다.

되살렸습니다. 그리고 주석에 이렇게 적어 뒀습니다 — 이걸 다시 빼려면 삭제 버튼을 비활성 상태로 노출하는 쪽이 먼저다.

숨김은 표시와 짝입니다. 어떤 요소를 조건부로 감출 때 진짜 비용은 감추는 일 자체가 아니라, 감춘 자리에 아무 말도 남기지 않는 데서 생깁니다.

5. 모달 위에 모달

같은 사진 화면에서 하나가 더 나왔습니다. 확대 보기에서 ✎ 를 눌러 제목·설명을 고치고 뒤로 가면, 사진이 아니라 목록으로 나왔습니다.

확대 보기가 네이티브 모달로 떠 있었기 때문입니다. 모달이 떠 있는 채로 다른 화면으로 이동하면 새 화면이 모달 아래로 깔려서 안 보입니다. 그래서 편집으로 갈 때 모달을 먼저 닫고 있었고, 닫은 뒤엔 돌아올 자리가 없었던 거죠.

확대 보기 자체를 라우트(페이지)로 바꿨습니다. 그랬더니 이번엔 편집 화면까지 모달처럼 아래에서 올라왔습니다. 네이티브 스택은 모달로 띄운 화면 위에 쌓은 것도 모달로 취급하거든요.

이 답은 이미 우리 코드 안에 있었습니다. 뽀모도로 화면의 레이아웃 파일에 이렇게 적혀 있더군요 — “네이티브 스택은 모달 뒤에 push 된 화면도 모달로 띄우므로, 모달 내부 push 는 중첩 스택 필수”. 몇 달 전의 제가 같은 벽에 부딪히고 남겨 둔 문장이었습니다. 첨부 사진도 같은 모양으로 묶었습니다. 그룹 전체를 풀스크린 모달로 띄우고, 편집은 그룹 안쪽 스택에서 밀어 올립니다. 화면은 모달로 뜨고, 그 안의 이동은 이동으로 읽힙니다.

오늘의 교훈

하나, 숨긴 것에는 표시가 따라야 합니다. 숨긴 열의 필터도, 조건부로 감춘 삭제 버튼도 마찬가지입니다. 감추는 결정은 대개 옳았고, 감춘 자리에 아무 말도 남기지 않은 것이 문제였습니다.

둘, 같은 모양은 한 가지 뜻만 가져야 합니다. 파란 채움 원이 한쪽에서는 오늘이고 다른 쪽에서는 선택이면, 두 달력을 나란히 놓는 순간 사용자는 매번 다시 배워야 합니다.

셋, 조립 목록이 두 벌이면 반드시 표류합니다. 상세와 미리보기, 그리고 인라인으로 복사돼 있던 스텝 그래프 두 벌. 합칠 때는 차이를 지우지 말고 남긴 채 합치는 게 안전합니다.

넷, 컨테이너가 자기 크기에 대해 하는 말을 믿지 마세요. 세로 스크롤을 켜면 가로도 잘리고, 손으로 적어 둔 팝오버 치수는 내용이 자라도 따라 자라지 않습니다.

기능이 늘어난 하루는 아니었습니다. 필터는 어제도 걸렸고 사진도 어제 지워졌습니다. 다만 이제 화면이 자기가 뭘 하고 있는지를 조금 더 말합니다.