사이드바를 자유롭게 커스텀할 수 있는 기능을 작업했다. 한 줄짜리 작업처럼 보였던 일이 한 번에 끝나지 않았다. 작업은 잘 마무리했지만, 회고를 하면서 관련된 지식을 쌓아두면 다음 번에 반응형 작업을 할 때 도움이 될 것 같았다.
문제 1. truncate가 한 번에 되지 않았다
겪은 문제
사이드바에 들어가는 논문 제목이 길어지면 두 줄, 세 줄로 흘렀다. 한 줄로 자르고 …으로 처리하면 깔끔해진다. Tailwind에 truncate 유틸이 있으니 제목에 붙였다. 안 됐다. 클래스 위치를 옮기거나 부모에 추가해도 결과는 같았다.
왜 그랬는가
CSS 레이아웃은 두 단계로 일어난다. 1단계에서 부모와 자식이 협상해 박스의 폭이 결정되고, 2단계에서 그 폭 안에 콘텐츠가 그려진다. truncate는 2단계에 개입하는 도구다. 정해진 박스 폭 안에서 텍스트가 넘치면 잘라낸다. 박스가 좁아지지 않으면 자를 거리도 생기지 않는다.
문제는 flex 자식의 기본 동작에 있었다. flex 자식의 min-width 기본값은 auto이며, 이는 콘텐츠의 자연스러운 최소 폭 밑으로는 줄어들지 않는다는 의미다. truncate가 적용하는 white-space: nowrap이 함께 걸리면, 그 "자연스러운 최소 폭"은 곧 텍스트를 한 줄로 펼친 폭과 같아진다. 결과적으로 박스가 그 폭 밑으로 줄어들지 않으며, 텍스트를 자를 여지도 생기지 않는다.
이 기본 동작을 해제하려면 min-width: 0을 명시해야 한다. 그리고 이 설정은 컨테이너 체인 전체에 적용되어야 한다. flex 자식의 최소 폭 요구는 부모로 전파되기 때문에, 가장 안쪽 텍스트에 설정해도 중간 컨테이너 중 한 곳이라도 누락되면 그 지점에서 전파가 막힌다.
어떻게 해결했나
가장 안쪽 텍스트에 truncate를 적용하고, 그 위의 모든 flex 컨테이너에 min-w-0을 적용했다.
<div className="flex min-w-0">
<div className="flex min-w-0">
<p className="truncate">긴 논문 제목</p>
</div>
</div>
truncate와 min-w-0은 함께 동작한다. truncate만 적용하면 박스가 좁아지지 않아 자를 거리가 없고, min-w-0만 적용하면 박스는 좁아지지만 텍스트가 여러 줄로 흐른다. 두 설정이 모두 있어야 한 줄 말줄임이 완성된다.
문제 2. minSize가 지켜지지 않았다
겪은 문제
사이드바를 직접 드래그로 조절할 수 있게 했다. 너무 좁히면 내부 콘텐츠가 깨지므로 react-split-pane(패널을 좌우로 분할하고 경계를 드래그하게 해주는 라이브러리)의 minSize 속성에 안전한 값을 지정했다. 그런데 특정 상황에서 사이드바가 그 값보다 더 작아졌다. 패널 안의 콘텐츠가 밖으로 밀려나, 토론 영역이 통째로 보이지 않는 증상까지 나타났다. 창을 옆으로 한참 늘려야 콘텐츠가 다시 보였다.
왜 그랬는가
첫 번째는 라이브러리의 동작 범위 문제다. react-split-pane의 minSize는 사용자가 divider를 직접 드래그할 때만 강제된다. 그러나 사이드바 폭이 바뀌는 경로는 그 외에도 여러 가지다. 브라우저 창 크기가 변할 때, 저장된 사이드바 폭을 페이지 로드 시 복원할 때, 모드가 전환될 때 등이다. 이런 상황에서 라이브러리는 기존 패널 크기들을 비율로 재분배하는데, 이 재분배 과정에서 minSize를 다시 검사하지 않는다. 결과적으로 명시한 값이 일부 경로에서는 지켜지지 않는다.
두 번째는 중첩 구조에서 오는 문제다. 우리 사이드바는 그 자체로 다시 분할 가능한 구조라서, 안에 또 split pane이 있다. 패널 하나의 minSize만 설정하는 것으로는 부족했다. 여러 패널이 가로로 배치되어 있다면, 사이드바 전체의 실질적 최소 폭은 각 패널 minSize의 합에 divider 폭을 더한 값이다. 이 값은 단일 속성으로 표현할 수 없고, 트리 전체를 순회해 계산해야 한다.
어떻게 해결했나
세 가지를 함께 도입했다.
첫째, 트리 전체의 최소 폭을 계산하는 getLayoutMinWidth() 함수를 만들었다. 이 함수는 현재 split pane 트리를 재귀적으로 순회하며 트리 전체가 요구하는 최소 폭을 산출한다.
둘째, 그 계산값을 ResizeObserver와 CSS 변수로 실시간 동기화했다. 계산 결과를 --right-sidebar-dynamic-min-width CSS 변수에 반영하고, 사이드바와 root 요소에도 inline style로 적용한다. 트리 구조가 변하면 ResizeObserver가 감지하여 다시 계산한다.
셋째, clamp 함수와 controlled size로 라이브러리의 재분배를 직접 보정했다.
return Math.min(Math.max(desiredFirstSize, firstMinSize), maxFirstSize);
이 함수는 들어온 값이 어떤 경로에서 왔든, 사용자 드래그든 복원된 저장값이든 라이브러리의 재분배 결과든, firstMinSize 밑으로 내려가지 않도록, 그리고 두 번째 패널의 secondMinSize를 침범하지 않도록 강제한다. 보정된 값은 <Pane size={...}>로 라이브러리에 직접 전달된다. 라이브러리가 자체적으로 비율을 계산하지 못하게 하고, 검증된 값만 적용되도록 통제하는 구조다.
라이브러리의 minSize가 명시적 의도였다면, clamp는 그 의도를 모든 경로에서 강제하는 실행 장치였다.
회고
반응형 시스템에 대한 학습
이번 작업은 단순한 버그 수정이라기보다, 앞으로 반응형 시스템을 잘 만들기 위해 필요한 사고 틀을 얻는 시간이었다. 두 가지가 특히 또렷해졌다.
하나는 부모-자식 간 관계성을 고려해야 한다는 점이다. CSS의 폭 협상이든 split pane이든, 한 컴포넌트의 동작은 그 자리에서 끝나지 않고 위아래로 영향을 주고받는다. 자식의 폭 요구가 부모로 전파되고, 부모의 제약이 자식의 렌더링을 결정한다. 이 양방향 관계를 인지하고 있어야 문제가 어디서 새는지 추적할 수 있다.
다른 하나는 고정값의 영향력 범위를 고려해야 한다는 점이다. 이전에는 어떤 값을 명시하면 그 값이 무조건 보장될 것이라고 단순하게 생각했다. 실제로는 그 값이 어디까지 영향을 미치는지가 핵심이었다. min-w-0을 한 곳에 설정해도 영향력이 체인 끝까지 닿지 않으면 의미가 없고, minSize를 명시해도 라이브러리가 일부 경로에서 무시하면 무력하다. 값을 설정하는 것과 그 값이 모든 상황에서 보장되는 것은 다른 일이었다.
상위 개념에 대한 아쉬움
이번 해결책들은 모두 보완의 방식이었다. truncate가 동작하지 않으니 min-w-0을 여러 레이어에 추가하고, minSize가 지켜지지 않으니 clamp로 강제한다. 문제가 생기는 자리마다 도구를 하나씩 더 얹는 구조다. 회고하면서 든 생각은, A 문제를 해결하려 B를 추가하는 방식이 아니라 A와 B가 애초에 발생하지 않도록 하는 C라는 상위 개념이 어딘가 존재할 것 같다는 점이었다. min-w-0을 여러 레이어에 깔아야 하는 패턴 자체가 번거로운데, 코드의 깔끔함을 추구하는 개발자들이라면 이미 더 나은 추상화를 만들어 두지 않았을까 싶다.
순간순간의 문제를 해결하는 관점에 머물러 있으면 계속 보완 도구를 쌓게 된다. 더 객관적인 시야와 폭넓은 지식 위에서 C 같은 근본적 접근을 찾아 쓸 수 있으면 좋겠다. 다음번에는 보완 도구를 얹기 전에 한 발 물러서서 그런 상위 개념부터 탐색해보려 한다.
AI 코딩 에이전트와 작업하며 든 생각
이번 작업의 실제 코드는 AI 에이전트(Codex)가 작성했다. 나는 문제를 정의하고, 시도를 검증하고, 방향을 잡는 역할이었다. 특히 truncate 작업에서 헤맸던 시기를 돌아보면, 내가 "긴 텍스트를 한 줄로 잘라줘"라는 표면적인 지시밖에 못 했다는 사실이 분명해진다. AI는 그 지시를 받아 truncate 클래스를 추가하지만, 그것이 동작하기 위한 조건(체인 전체의 min-w-0)을 내가 모르면 결과를 검증할 수 없다. AI가 "적용했습니다"라고 답하면 그 말을 그대로 받아들이는 수밖에 없다.
CSS의 두 단계 협상, 폭 요구의 전파 방식, min-w-0의 역할을 이해한 뒤로는 지시 자체가 달라졌다. "truncate가 동작하려면 박스가 좁아질 수 있어야 하므로, 컨테이너 체인 전체에 min-w-0이 적용되어 있는지 확인하고 누락된 곳을 채워줘"는 다른 종류의 지시다. AI에게 시킬 수 있는 일은 결국 내가 아는 만큼이라는 사실이 이번 작업에서 또렷하게 남았다. AI가 코드를 대신 작성해주는 환경일수록 시스템을 이해하는 사람의 가치는 오히려 올라간다고 느낀다. 작업이 끝난 뒤 무엇이 왜 되고 안 됐는지 정리하는 시간을 앞으로도 자주 가지려 한다. 굿!
'Learn everyday' 카테고리의 다른 글
| [6월 TIL #5] 작업 회고: Design Token-Based UI Architecture (0) | 2026.06.09 |
|---|---|
| [6월 TIL #4] 유저가 느낀 가치를 어떻게 측정할 것인가 (0) | 2026.06.09 |
| [6월 TIL #2] 다른 근육 쓰기: 2주년 이벤트 & 글로벌 유저 인터뷰 (0) | 2026.06.04 |
| [6월 TIL #1] AI에게 했던 잡도리를 복기해봤다 (0) | 2026.06.01 |
| [5월 TIL #17] 폰으로만 코딩③: 느슨해진 업무에도 긴장감이 필요하다 (0) | 2026.05.31 |