02
제약
의존성 0, 빌드 단계 없음. 데이터는 맥의 SQLite 파일 하나이고, 모든 변경이 append-only 이력에 남아 배치 단위로 되돌릴 수 있어야 했다.
포기한 것
로그인을 앱에 만들지 않았다. 사용자 표·세션·해시·시도 제한이 전부 따라오고 권한 모델을 통째로 갈아야 한다. 인증은 앞단에 맡기고 앱은 단순하게 뒀다.
남은 것
배포 18건 대 업무 11건이라는 실측이 기능 우선순위를 뒤집었다. 27일째 미완료인 배포가 화면에 아무 표시도 없다는 것을 발견해 지연 신호를 붙였다.
이 노트의 표기 규칙 회사·서비스·동료 실명, 사내 Slack/Jira 주소, 토큰, 사내 IP는 모두 제외하거나 가명으로 대치했다. 팀원은
팀원 A~G, Jira 프로젝트는PROJ, 티켓은PROJ-1234형태로 적는다. 판단 근거가 되는 집계 수치는 그대로 남긴다 — 그게 없으면 왜 그렇게 만들었는지 알 수 없다.
무엇 / 목표
팀원 7명의 업무와 배포 일정을 한 화면에서 보고 관리하는 로컬 도구. 사내 Slack 멘션과 사내 Jira 티켓을 끌어와 캘린더 항목으로 연결한다.
- 의존성 0. Node 24 +
node:sqlite+ 정적 파일. 빌드 단계 없음 - 데이터는 내 맥의 SQLite 파일 하나. 원격 접속자는 읽기 전용
- 모든 변경은 append-only 이력에 남고 배치 단위로 되돌릴 수 있다
내 역할 · 기여
기획부터 구현·검증·운영까지 혼자. 작업 원칙 두 가지가 계속 효과를 봤다.
- 만들기 전에 실제 데이터를 잰다. 감으로 기능을 붙이지 않는다
- 화면으로 확인한다. 코드가 맞아도 화면에서 틀린 경우가 여러 번 있었다
2026-08-26 작업 (순서대로)
1. 변경 이력 창 확대 · 스크롤바 숨김
560px에서는 한 줄짜리 변경도 접혀서 훑기 어려웠다. 880px로 넓히고 목록 높이를 66vh까지 줬다. 스크롤 막대는 이 창에서만 감췄다 — 막대를 없애면 "더 있다"는 신호도 사라지므로 앱 전체에는 적용하지 않았다.
- 회귀 검사를 쓰다
.history-body가 이름과 달리 6개 다이얼로그가 공유하는 클래스임을 발견. 셀렉터를 이력 창 안으로 한정.
2. 외부 공개 대비 접근 통제 (중요)
바깥에서 보려고 터널을 붙이면 권한 판정이 무너지는 구멍을 발견했다.
- 권한을 요청 소켓 주소로만 판단했는데, 프록시는 같은 기계의
127.0.0.1에서 접속한다. → 바깥에서 온 사람이 전부 "나"가 되어 수정·삭제·DB 전체 내보내기까지 열린다. 읽기 전용 배지도 안 뜬다. → 통로를 나눴다. 터널이 가리킬 별도 포트로 들어온 요청은 주소와 무관하게 원격으로 취급. 표시를 붙이는 쪽이 서버 자신이므로 헤더처럼 위조할 수 없다. - CSRF가 실제로 열려 있었다. 본문 파서가
content-type을 안 보고Origin검사도 없어서, 아무 웹사이트나 열어 두면 그 페이지가localhost로 POST를 보낼 수 있었다 (text/plain이면 preflight 없이 통과 → 응답은 못 읽어도 쓰기는 일어난다). →Sec-Fetch-Site·Origin·content-type세 겹으로 막음. 본문 없는 POST는 그대로 통과. .env가 644였다 →600. 같은 맥의 다른 계정이 토큰을 읽을 수 있었다.
3. 상시 실행 · 매일 백업 (launchd)
백업이 없다는 게 가장 큰 실질 위험이었다. DB 본체 270KB인데 WAL이 4.1MB —
.db 파일만 복사하면 최근 데이터가 통째로 빠진다.
VACUUM INTO로 WAL 포함 일관된 사본을 파일 하나로 만든다. 만든 뒤 그 사본을 열어 건수를 센다 — 열리지 않는 백업은 백업이 아니다- launchd에 둘 등록: 서버 상시 실행(KeepAlive, 죽으면 재기동)과 매일 09:00 백업
- 토큰은 plist에 넣지 않는다. plist는 평문이고 다른 계정이 읽는다
- 테스트를 쓰다
VACUUM INTO는 대상 파일이 있으면 실패한다는 것을 발견 → 빈 이름 탐색
4. 팀·배포 관리 신호 셋
먼저 실제 데이터를 쟀다. 이 도구는 사실상 배포 관리 도구였다 (배포 18건 vs 업무 11건). 그런데 배포 판단에 필요한 신호 셋이 빠져 있었다.
| 발견 | 붙인 것 |
|---|---|
| 배포 하나가 27일째 미완료인데 화면에 아무 표시가 없었다 | 지연 — 칩에 지연 N일, 상단에서 임박보다 앞 |
| 하루에 배포 7건이 몰린 날이 캘린더에서 안 보였다 | 날짜 옆에 배포 N, 3건 이상 강조 |
| 티켓 연결 24건인데 얼마나 끝났는지 볼 수 없었다 | 하위 티켓 진행률 2/5 |
- 지연을 처음엔 현재 달에서만 찾다가 달을 넘기면 사라지는 것을 스크린샷으로 발견. 서버가 화면 범위와 무관하게 뽑도록 고침 — 지난달 것이 사라지면 경고의 의미가 없다
- 진행률은 티켓이 없으면 아무것도 안 보인다.
0/0을 0%로 그리면 "시작도 안 했다"는 거짓 신호
5. 배포만 보기 토글
배포가 주력인데 업무·휴가와 섞여 파이프라인만 훑을 방법이 없었다. 캘린더·현황판·작업자판에 같은 기준을 건다. 휴가는 남긴다 — 누가 자리에 없는지는 배포 판단에 필요하다.
6~7. 두 가지 결함 정리
- 현황판이 빈 이유를 틀리게 안내했다 (
배포만때문인데 "소유자 필터"라고 함) → 엉뚱한 필터를 찾게 만든다 - 작업자판 배치 검사가 18종 연속 실행 중 1회 실패. 값이
8,8,8이라 레이아웃 전 상태를 잰 것. 고정 대기 → 조건 대기로 변경
8. Jira 티켓 붙이기 · 떼기
배포 18건 중 티켓이 붙은 건 3건뿐인데 미담김 티켓은 46건이었다. 진행률을 만들어도 연결이 없으면 빈 화면이다. 연결 마찰을 없앴다.
- 항목 상세의 Jira 탭에서 바로 붙인다. 후보는 아직 안 붙은 티켓, 그 항목 담당자 것이 먼저
- 떼기는 지금까지 아예 없었다. 잘못 매달면 DB를 직접 고치는 것 말고 방법이 없었다. 되돌릴 수 없으면 사람은 붙이기를 주저한다 — 연결률이 낮았던 이유 중 하나였을 것이다
- 떼도 티켓 자체는 남긴다. Jira 쪽 사실이지 내가 만든 게 아니다
- 붙임·뗌 모두 이력에 남기되 되돌리기(⌘Z) 대상은 아니다 — 되돌려도 Jira 연결이 바뀌지 않으니 되돌릴 수 있는 척하면 거짓말이 된다
9. 휴가도 비근무일에는 표기 제거
토·일·공휴일·대체공휴일에 아무것도 그리지 않는 규칙을 휴가까지 확대. 쉬는 날에 "휴가 중"이라고 적어 봐야 알려 주는 것이 없다. 기간이 연휴를 가로지르면 조각으로 갈린다. 근무일이 하나도 없는 휴가(토요일 하루짜리)는 그대로 그린다 — 감추면 손댈 수 없는 데이터가 된다.
테스트 두 개를 바로잡았다. ① 내 기대값이 틀렸다(대체공휴일을 빼먹었다) ② E2E 정규식의
\d가 템플릿 리터럴 escape로 죽어 값이 항상 1로 읽혔다.[0-9]로 바꾸니 참값이 드러났다 — 그럴듯해 보이는 초록 테스트가 아무것도 검증하지 않고 있었다.
10. UI/UX — 실측해서 고침
감이 아니라 재고 고쳤다. 색은 문제가 아니었다 (본문 14.8, 보조 6.8 — 둘 다 WCAG AA 통과).
| 항목 | 전 | 후 |
|---|---|---|
| 달력 | 1114px 내용 / 470px 공간 (절반 이상 잘림) | 잘림 없음 |
| 달력 위 영역 | 279px (화면의 34%) | 141px |
| 상단바 | 162px · 2줄 · 버튼 23개 | 97px · 1줄 |
| 다가오는 7일 | 칩 13개가 3줄 | 1줄 + 우측 페이드 |
| 주 높이 / 표시 건수 | 90~316px 들쭉날쭉 | 균일 · 4건 |
| 10px 글자 | 50개 | 1개 |
"달을 보려고 여는 화면인데 달을 못 보는" 상태였다. 6주가 높이를 똑같이 나눠 갖고,
넘치는 주는 +N 더로 접고 눌러서 펼친다. 사람별 보기는 접지 않는다 —
밴드 머리글까지 있어 접으면 대부분이 가려지고 드래그도 못 하게 된다.
이 과정에서 잡은 것:
repeat(6, 1fr)은 auto 최소값 때문에 행이 늘어난다 →minmax(0, 1fr)- 컨테이너가
min-height에 머물러 여백이 남는데도 접혔다 →height: 100% - 접힌 요소는 높이가 0으로 읽혀
offsetTop으로 재면 접을 양을 잘못 계산 → 줄 단위로 셈 - 줄 높이를 고정하니 칩이 늘어나 휴가의 낮은 알약(17px)이 사라졌다 →
align-self: start - 밝은 테마를 하루 종일 한 번도 보지 않았다는 걸 깨달았다. 확인하니 보조 텍스트가 4.52로 간신히 통과하는 수준 → 5.57로
오늘의 판단 (코드 아님)
바깥에서 접근하는 방법
로그인을 앱에 직접 만들지 않는다. 사용자 표·세션·해시·시도 제한이 다 따라오고, 지금의 권한 모델을 통째로 갈아야 한다. 인증은 앞단(터널)에 맡기고 앱은 단순하게 둔다.
- 내 기기만 → Tailscale (도메인 불필요, WiFi 바뀌어도 주소 그대로)
- 팀원도 본다 → Cloudflare Tunnel + Access (설치 불필요, 도메인 필요)
- 공유기 포트 포워딩은 안 된다 — 인증이 아예 없는 앱이다
절전과 상시 가동
절전에 들어가면 프로세스가 멈추고 터널 연결도 끊긴다. Power Nap이나 Wake on LAN은 이 앱을 깨우지 못한다. 깨어나면 프로세스는 이어서 돌고 Slack은 스스로 재연결한다. launchd가 필요한 건 절전이 아니라 재부팅·크래시 때다.
"부재중에도 관리"의 진짜 병목은 인증이 아니라 상시 가동이다. 노트북을 들고 다니면 어떤 터널을 붙여도 소용없다.
오늘 상태
- 커밋 12개. 단위 테스트 153, E2E 18종 전부 통과
- 서버는 launchd로 상시 실행, 매일 09:00 백업
- 다음에 효과 큰 것: 남은 배포 항목에 티켓 붙이기. 이제 항목을 열고 탭에서 고르면 끝이고, 하위 티켓이 모두 완료되면 완료가 자동 체크된다