docs(TechLog): 주제 7~10 을 다시 쓴다

주소가 게시 시점에 굳어 저장되는 구조, 축 링크를 두 번 옮긴 순서, 한글 slug 가
간헐적으로 보인 두 가지 어긋남을 표로 갈랐다. 화면이 실패를 없음으로 그릴 때 작성
도구에서 왜 더 오래 숨는지, Promise.all 이 거절과 던짐에서 다른 경로를 타는 이유를
채웠다. CSS module 이 왜 전역 규칙에 닿지 않는지, 403 과 404 가 원인을 어떻게
좁혔는지도 적었다.

link-audit.py 를 감사 Case 의 evidence 로 걸어 배정한 증거 하나를 메웠다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-09-07 19:06:20 +09:00
co-authored by Claude Opus 5
parent 193da20d09
commit b1653dbba8
15 changed files with 204 additions and 92 deletions
@@ -61,23 +61,29 @@ tech-log-backend : 911e8ba — 없던 두 컨트롤러를 구현
편집기는 질문 목록을 받아 고를 수 있게 그린다. 목록이 비면 「이 프로젝트에 열린 질문이 없습니다」를 적는다.
요청이 실패했을 때도 빈 배열이 되고 있었다.
요청이 실패했을 때도 빈 배열이 되고 있었다. 그래서 「없다」와 「못 읽었다」가 같은 화면이 됐다.
서버는 404 를 주고 있었다. 그 두 목록 조회에 컨트롤러가 없었고, 계약에는 선언돼 있어 프론트가 그것을 믿고 불렀다.
## 작성자가 무엇으로 읽었나
작성자는 자기가 쓴 것과 화면을 대조한다. 화면이 「없습니다」라고 하면 아직 안 썼거나 게시하지 않았다고 읽는다.
이 경우에는 넷이 있었고 공개 사이트에도 나오고 있었다. 편집기만 못 읽고 있었다.
이 경우에는 넷이 있었고 공개 사이트에도 나오고 있었다. 두 화면이 같은 데이터베이스를 보는데 한쪽만 비어 있었으므로, 공개 사이트를 함께 보지 않으면 알아챌 방법이 없었다.
작성 도구에서 이 부류가 특히 오래 숨는다. 읽기 전용 화면이면 방문자가 「데이터가 없나 보다」로 넘어가지만, 작성 도구에서는 그 오독이 곧바로 작업 판단이 된다 — 없다고 읽으면 다시 쓰게 된다.
## 못 읽었다고 적는다
> 거짓말을 하느니 못 읽었다고 말한다.
요청이 실패하면 실패했다고 적는다. 0건은 0건이라고 적는다.
요청이 실패하면 실패했다고 적 0건은 0건이라고 적는다. 이 둘을 구분할 수 있어야 작성자가 다음에 무엇을 할지 정한다 — 실패면 다시 부르거나 서버를 보고, 0건이면 쓰면 된다.
## 같은 판단을 다른 화면에
주제 탭에도 같은 판단을 적용했다. 탭 하나를 못 받아도 탭 줄과 나머지 탭은 그대로 그려지고, 못 받은 탭에는 못 받았다고 적는다.
주제 탭에도 같은 판단을 적용했다. 탭 하나를 못 받아도 탭 줄과 나머지 탭은 그대로 그고, 못 받은 탭에는 못 받았다고 적는다.
탭 줄이 남는 것이 중요하다. 탭 줄까지 사라지면 그 주제가 없는 것처럼 보이고, 그러면 못 읽은 범위가 화면에서 더 넓어진다.
## 확인하지 못한 것
@@ -58,19 +58,27 @@ tech-log-frontend : 6e784ed · fd73bc8 · 3bb724b
## 묶어 읽으면 한쪽이 전체를 끌고 내려간다
편집기가 질문 목록과 결정 목록을 하나로 묶어 기다리고 있었다. 결정 쪽 연산에 컨트롤러가 없어 404 가 났고, 화면은 두 목록을 다 못 받은 것으로 그렸다.
편집기가 질문 목록과 결정 목록을 하나로 묶어 기다리고 있었다. 결정 쪽 연산에 컨트롤러가 없어 404 가 났고, 화면은 두 목록을 다 못 받은 것으로 그렸다.
질문 목록은 정상적으로 오고 있었다. 둘을 따로 읽도록 갈랐다.
질문 목록은 정상적으로 오고 있었다. 묶어 기다리면 하나라도 거절되는 순간 전체가 거절되므로, 성공한 응답이 있어도 쓸 수 없다.
## 동기적으로 던지면 거절 처리를 지나지 않는다
둘을 따로 읽도록 갈랐다. 실패한 목록에만 못 받았다고 적고 나머지는 그대로 그린다.
## 거절과 던짐이 다른 경로를 탄다
같은 코드에서 더 미묘한 변종이 났다.
> `Promise.all([gateway.foo()])` 은 foo 가 **거절하는 것만** 잡는다. 호출이 **동기적으로 던지면** 배열을 만드는 중에 터져 rejection handler 를 지나지 못하고, 그러면 홈 focus 한 칸 때문에 대시보드 전체가 빈 화면이 된다.
배열을 만드는 표현식 안에서 던지면 그 표현식이 완성되지 않으므로 거절 처리기 붙을 대상이 없다.
배열 리터럴 안에서 함수를 부르는 시점은 `Promise.all` 에 배열이 넘어가기 전이다. 그 호출이 동기적으로 던지면 배열이 완성되지 않으므로 `Promise.all` 이 시작조차 하지 않고, 거기에 붙인 거절 처리기 붙을 대상이 없다.
거절만 잡는 처리로는 부족하다. 던지는 경로도 함께 잡아야 한 칸의 실패가 화면 전체로 번지지 않는다.
## 탭에도 같은 판단을
탭 줄은 목록 하나로 그리고 상세는 고른 탭만 받는다. 상세 하나를 못 받아도 탭 줄과 나머지 탭은 그대로 그리고, 못 받은 탭에는 못 받았다고 적는다.
탭 줄은 목록 호출 하나가 주는 전부이고 상세는 고른 탭만 그때 받는다. 상세 하나를 못 받아도 탭 줄과 나머지 탭은 그대로 그리고, 못 받은 탭에는 못 받았다고 적는다.
이 구조에서는 실패가 번질 수 있는 범위가 처음부터 한 탭으로 좁다. 상세를 미리 다 받아 두는 구조였다면 하나가 실패할 때 어디까지 그릴지를 다시 정해야 했다.
## 확인하지 못한 것
@@ -62,20 +62,30 @@ tech-log-design-package : 76a7ccb
## null 을 돌려주고 걸러 내면 흔적이 없다
매퍼가 아는 종류가 아니면 `null` 을 돌려준다. 호출부는 그 목록에서 `null` 을 걸러 낸다.
매퍼가 아는 종류가 아니면 `null` 을 돌려주고, 호출부는 그 목록에서 `null` 을 걸러 낸다.
이 조합에서는 오류가 나지 않고 빈 줄도 생기지 않는다. 목록의 길이만 줄어든다.
| 무엇이 남나 | 있음 |
|---|---|
| 오류 로그 | x |
| 화면의 빈 줄 | x |
| 목록 길이의 변화 | o |
목록에 몇 개가 있어야 하는지 아는 사람만 알아챌 수 있고, 공개 화면에서는 그것을 아는 사람이 작성자뿐이다.
## 응답 모양이 다른 목록에 다른 매퍼를 썼다
프로젝트 기록 목록과 탐색의 지식 목록은 응답 모양이 다르다. 프로젝트 쪽은 관계 항목을 그대로 실어 요약도 주제도 게시일도 없었고, 지식 목록은 처음부터 그 칸들을 갖고 있었다.
모양이 다른데 매퍼를 공유하면서 종류 판정까지 그쪽 것을 따랐다.
모양이 다른데 매퍼를 공유하면서 종류 판정까지 그쪽 것을 따랐다. 지식 목록의 매퍼는 CASE 와 REFERENCE 만 다루면 되는 화면을 위해 만들어졌으므로, 질문과 개념을 받으면 `null` 을 돌려준다.
## 고친 것
응답 모양에 맞는 매퍼를 쓰고 모든 종류를 싣게 했다. 목록 항목에 요약과 주제와 게시일을 더해 「제목만 있고 가운뎃점만 남은」 줄을 없앴다.
이 목록은 계약도 함께 고쳐야 했다. 관계 항목을 그대로 싣던 구조라 요약·주제·게시일을 담을 칸이 응답에 없었다.
## 확인하지 못한 것
`null` 을 돌려주고 호출부가 거르는 매퍼가 다른 목록에도 남아 있는지는 세지 않았다.
@@ -29,23 +29,27 @@ source:
매번 우는 검사가 읽히지 않게 되는 것을 막는다.
> 매번 늑대를 외치는 검사는 읽히지 않게 되고, 진짜 실패가 그 옆에 눈에 띄지 않은 채 앉아 있게 된다.
## 규칙
### 1. 기대된 실패는 조건을 적어 뺀다
미리보기가 아직 없는 문서는 현재 미리보기를 물으면 404 를 답하고, 화면은 그것을 「미리보기를 만드세요」로 바꾼다. 이런 응답은 실패가 아니다.
미리보기가 아직 없는 문서는 현재 미리보기를 물으면 404 를 답하고, 화면은 그것을 「미리보기를 만드세요」로 바꾼다. 이 404 는 정상 동작이 오류 상태 코드로 나타난 것이지 실패가 아니다.
로그인 전 세션 탐침의 401 도 같은 부류다. 어느 쪽도 사람이 고칠 것이 없다.
### 2. 뺀 나머지는 전부 실패시킨다
조건에 걸리지 않는 4xx 와 5xx 는 모두 스윕을 실패시킨다.
조건에 걸리지 않는 4xx 와 5xx 는 모두 스윕을 실패시킨다. 「이건 아마 괜찮을 것」이라는 판단을 스윕 안에 두지 않는다.
### 3. 조건을 적을 수 없으면 빼지 않는다
조건 없이 빼면 진짜 실패도 같이 빠진다.
조건 없이 빼면 진짜 실패도 같이 빠진다. 어떤 요청이 왜 그 응답을 받는지 한 줄로 적을 수 없으면 그것은 아직 기대된 실패가 아니다.
### 4. 뺀 조건을 사람이 읽을 수 있는 곳에 남긴다
왜 그 응답이 기대된 것인지 적혀 있지 않으면 다음 사람이 조건을 넓힌다.
왜 그 응답이 기대된 것인지 적혀 있지 않으면 다음 사람이 조건을 넓힌다. 비슷한 404 가 하나 더 나왔을 때 같은 조건에 넣을지 판단할 근거가 없기 때문이다.
## 적용 조건
@@ -56,11 +60,10 @@ source:
## 예외
- 기계가 판정하고 사람이 결과를 읽지 않는 검사라면 빨간 줄이 쌓여도 무뎌지지 않는다. 그래도 통과 기준은 정해야 한다.
- 그 응답이 정말 고쳐야 하는 것이면 조건으로 빼지 말고 고친다. 뺄지 고칠지는 사람이 고칠 것이 있는가로 가른다.
## 예시
- 미리보기가 없는 문서의 404 를 스윕이 실패로 셌다. 건강한 배포 아래에 매번 같은 빨간 줄이 남았다.
> 매번 늑대를 외치는 검사는 읽히지 않게 되고, 진짜 실패가 그 옆에 눈에 띄지 않은 채 앉아 있게 된다.
- 로그인 전 세션 탐침의 401 도 같은 부류라 같은 조건으로 제외하고, 나머지 4xx·5xx 는 전부 스윕을 실패시킨다.
- 미리보기가 없는 문서의 404 를 스윕이 실패로 셌다. 건강한 배포 아래에 매번 같은 빨간 줄이 남았다
- 로그인 전 세션 탐침의 401 도 같은 부류라 같은 조건으로 제외했다
- 나머지 4xx·5xx 는 전부 스윕을 실패시킨다
@@ -28,7 +28,9 @@ source:
## 목적
작성자가 「아직 안 썼다」와 「못 읽었다」를 구분할 수 있게 한다. 이 둘이 같은 화면이면 작성자는 다음에 무엇을 할지 정할 수 없다.
작성자가 「아직 안 썼다」와 「못 읽었다」를 구분할 수 있게 한다.
이 둘이 같은 화면이면 작성자는 다음에 무엇을 할지 정할 수 없다. 실패면 다시 부르거나 서버를 봐야 하고 0건이면 쓰면 되는데, 화면이 「없습니다」 하나로 답하면 뒤쪽으로 읽고 다시 쓰게 된다.
## 규칙
@@ -36,17 +38,25 @@ source:
빈 배열로 삼키지 않는다. 0건과 실패는 다른 문구를 쓴다.
목록 응답을 받아 그리는 코드에서 실패 경로가 빈 배열을 만들면, 그 아래의 「비어 있으면 이 문구」 분기가 두 경우를 같은 화면으로 만든다. 실패를 빈 값으로 접는 지점을 없애야 한다.
### 2. 한 칸의 실패가 옆 칸을 끌고 내려가지 않게 한다
여러 목록을 하나로 묶어 기다리면 한쪽의 실패가 전체를 실패로 만든다. 따로 읽고 실패한 목록에만 적는다.
여러 목록을 하나로 묶어 기다리면 하나라도 거절되는 순간 전체가 거절된다. 성공한 응답이 있어도 쓸 수 없다.
따로 읽고 실패한 목록에만 적는다. 화면에 여러 묶음이 있으면 실패가 번질 수 있는 범위를 그 묶음 하나로 좁힌다.
### 3. 거절만 잡는 처리로는 부족하다
호출이 동기적으로 던지면 그 처리기를 지나지 않는다. 던지는 경로도 잡는다.
호출이 동기적으로 던지면 그 처리기를 지나지 않는다. 배열 리터럴 안에서 부르는 함수가 던지면 배열이 완성되지 않으므로, 거기에 붙인 거절 처리기도 붙을 대상이 없다.
던지는 경로도 함께 잡아야 한 칸의 실패가 화면 전체로 번지지 않는다.
### 4. 항목을 걸러 낼 때 걸러 낸 것을 세어 둔다
매퍼가 모르는 종류에 빈 값을 돌려주고 호출부가 거르면, 목록이 한 줄 짧아지는 것 말고는 흔적이 없다.
매퍼가 모르는 종류에 빈 값을 돌려주고 호출부가 그것을 거르면, 오류도 빈 줄도 남지 않고 목록 길이만 줄어든다.
목록에 몇 개가 있어야 하는지 아는 사람만 알아챌 수 있다. 공개 화면에서는 그것을 아는 사람이 작성자뿐이다.
## 적용 조건
@@ -58,13 +68,11 @@ source:
## 예외
- 정말로 0건인 것과 못 읽은 것을 구분할 수 없는 화면이라면 그 구분을 먼저 만든다. 구분 없이 문구만 바꾸면 0건이 실패로 읽힌다.
- 읽는 사람이 그 데이터를 만들지 않는 화면 — 공개 조회 — 에서는 실패를 화면 전체의 오류로 다뤄도 된다.
- 읽는 사람이 그 데이터를 만들지 않는 화면에서는 실패를 화면 전체의 오류로 다뤄도 된다. 방문자에게는 어느 목록이 실패했는지가 할 일을 바꾸지 않는다.
## 예시
- 「이 프로젝트에 열린 질문이 없습니다」가 적혀 있는 동안 그 프로젝트에는 질문이 넷 있었고 공개 사이트에도 나오고 있었다.
- 탭 하나를 못 받아도 탭 줄과 나머지 탭은 그대로 그리고, 못 받은 탭에는 못 받았다고 적는다.
- 매퍼가 모르는 종류에 빈 값을 돌려주고 호출부가 걸러 내, 질문과 개념이 목록에서 사라졌다.
- 「이 프로젝트에 열린 질문이 없습니다」가 적혀 있는 동안 그 프로젝트에는 질문이 넷 있었고 공개 사이트에도 나오고 있었다
- 결정 목록만 404 인데 함께 묶어 읽은 질문 목록까지 「불러오지 못했습니다」가 됐다
- 탭 하나를 못 받아도 탭 줄과 나머지 탭은 그대로 그리고, 못 받은 탭에는 못 받았다고 적는다
- 매퍼가 모르는 종류에 빈 값을 돌려주고 호출부가 걸러 내, 질문과 개념이 목록에서 사라졌다