목록에 아무것도 없을 때 새 자료를 등록하라고 안내해도 될까. 조회에 실패했거나 권한이 없다면 그 문장은 문제를 설명하지 못한다.

나는 빈 화면의 문구부터 쓰기보다 어떤 사실을 알고 있는지 먼저 나눴다. 새 합성 상태 모듈에서 조회 세대와 결과를 바꾸며 각 상태를 확인했다.

아직 조회하지 않은 상태를 비어 있음으로 말하지 않는다

load_state는 initial에서 시작한다. begin_load를 호출하면 generation을 올리고 loading으로 바뀐다. 정상 응답을 받은 뒤에야 ready 또는 두 가지 empty 상태로 나눈다. 아직 요청하지 않았다는 사실과 성공해서 자료가 없음을 확인했다는 사실이 같은 문구로 표시되지 않게 하려는 선택이다.

이 모델의 행은 합성 ID 문자열뿐이다. 목록의 실제 데이터 형식이나 API는 연결하지 않았다. 화면의 뼈대, 로딩 표시 길이와 스크린리더 안내도 구현 범위가 아니다. 상태 이름을 정한 것과 사람들이 그 차이를 알아보게 만든 것은 다른 단계라서 실제 화면 검수가 필요하다는 전제를 남겼다.

처음 등록할 일과 조건을 풀 일

정상 응답이 빈 배열이라도 필터 여부에 따라 empty_initial과 empty_filtered를 나눈다. 전자는 첫 등록 경로를, 후자는 조건 해제나 검색어 수정을 설명하는 출발점이 된다. 실제 회사의 업무 절차를 추정해 생성 버튼을 무조건 보여 주지는 않는다. 등록 권한도 별도로 확인해야 한다.

filtered는 이번 예제에서 신뢰할 호출자가 주는 참·거짓 값이다. 실제 서비스에서는 조건 객체를 정규화해서 기본 조회와 달라졌는지 판단해야 한다. 이 작은 모델이 모든 필터의 의미를 자동으로 아는 것은 아니다. 조건 의미를 만드는 계층과 결과 상태를 고르는 계층을 연결할 계약으로 다뤘다.

실패한 새로고침에는 이전 자료임을 붙인다

한 번 성공한 목록이 있고 다음 조회가 실패하면 이전 행을 남기되 stale로 표시한다. 아무것도 없었던 조회 실패는 error다. 이전 내용이 남아 있다는 이유로 최신 조회가 성공한 것처럼 ready로 두지 않았다. retry도 참으로 돌려주어 다시 확인할 수 있는 상황임을 분리했다.

조건을 바꿨다면 남은 행이 이전 조건의 자료일 수도 있다. 실제 화면은 그 이전 기준을 함께 보관해 표시하거나 목록을 숨기는 정책을 선택해야 한다. 이번 코드는 이전 행을 보존하는 최소 모델이며 행에 조건 설명을 붙이는 UI는 없다. 최신 필터가 적용됐다는 문구를 이 상태만으로 만들어서는 안 된다.

권한 거절과 늦은 응답의 처리

denied 응답은 이전 행을 지우고 재시도를 자동 권장하지 않는다. 권한 없는 자료를 단순한 빈 검색 결과처럼 다루지 않기 위해서다. 다만 권한이 바뀐 순간 서버에서 접근을 막는 것은 별개다. 화면 배열을 지웠다는 이유로 이미 읽은 자료가 회수되거나 API 권한이 구현된 것은 아니다.

이전 generation의 응답은 현재 상태를 바꾸지 않는다. 새 조건의 조회 뒤에 예전 결과가 돌아와도 화면 상태를 되돌리지 않는 합성 검사다. 현재보다 미래인 번호나 잘못된 결과는 거절한다. 요청 취소와 실제 서버의 동작을 중단하는 코드는 없으며 응답 적용 순서만 다룬다.

문구를 고르기 전에 상태를 재현한다

테스트에서는 기본 빈 결과와 필터 빈 결과를 각각 만들고 오류를 빈 목록으로 바꾸지 않는지 확인했다. 성공 다음 실패가 stale인지, 거절이 예전 행을 지우는지, 이전 요청이 최신 상태를 덮지 않는지도 검사했다. 재현 가능한 상태가 있으면 문구 검토에서도 어떤 상황을 설명하는지 합의하기 쉽다.

공유 모듈은 순수 함수와 고정 실행 기록이며 실제 화면을 새로 배포한 것이 아니다. 각 상태에 맞는 버튼, 도움말과 접근성 안내는 적용할 사이트의 구성에 맞춰야 한다. 나는 보기 좋은 빈 화면 하나보다 사용자가 지금 무엇을 알고 다음에 무엇을 할 수 있는지 정확히 말하는 기준을 남기고 싶었다.