여섯 축을 동시에 훑고, 겹치는 발견은 합치고, 무거운 것은 코드로 다시 확인했다.
치명적인 것은 전부 화면이 멀쩡해 보이는 종류다. 값이 틀리게 보이거나, 알림이 안 오거나, 없는 일이 기록되는데 어디에도 흔적이 없다. 반대로 구조는 예상보다 훨씬 좋다 — 닫힘 판정·그림 검증·저장 원자성처럼 한 번 잘 잡아 둔 자리는 그 뒤 모든 기능이 그 위에 안전하게 쌓였다.
앞의 다섯은 한 시간 안쪽이거나 반나절이다. 여섯 번째는 그보다 크지만, 앞의 셋이 왜 동시에 존재했는지를 설명하는 항목이라 같이 둔다.
근거 App.tsx:343-348 — 청산 이펙트 의존성에 barsReady가 없다. 바로 위 리플레이 훅은 같은 위험 때문에 그 가드를 받아 쓴다(App.tsx:287).
왜 문제인가 심볼을 바꿨다 돌아오는 한 렌더 동안 봉 배열은 아직 이전 심볼 것이다. 그 틈에 ETH 저가가 BTC 청산선을 뚫은 것으로 읽히면 잔고가 깎이고 포지션이 사라지고 체결 기록에 청산 한 줄이 영구히 남는다. 되돌리는 길은 판 초기화(전부 삭제)뿐이다.
고치는 방향 이펙트 첫 줄에 if (!barsReady) return;. 그리고 “옛 배열을 잘라도 해롭지 않다”는 주석은 계좌가 생긴 뒤로 거짓이 됐으니 같이 고친다.
once 알림이 영구 소실된다근거 server/main.ts:254-257 — 저장이 먼저, 발송이 나중. server/ntfy.ts:108-124의 publish는 실패를 삼키고 재시도가 없다.
왜 문제인가 조건에 닿는 순간 firedAt이 박히고 알림이 꺼진다. 그 5초 사이 ntfy가 5xx면 폰엔 아무것도 안 오는데 알림은 이미 꺼져 있다. 화면은 “울렸음”이라고 말한다.
고치는 방향 publish가 성공 여부를 돌려주고, 실패면 enabled:false와 원장 claim을 되돌린다. 최소 2~3회 재시도.
근거 server/main.ts:99-108의 noteExchangeFailure가 실패 한 건마다 사다리를 올리는데, 호출 지점이 시세·봉·ABC 상위봉·다람쥐 넷이고 다람쥐는 스트림 8개 각각에서 부른다(main.ts:486).
왜 문제인가 30→60→120→300초로 점진적으로 쉬려던 설계가 한 tick 안에서 끝 칸까지 간다. 그리고 물러서는 동안 tickPrices까지 통째로 건너뛰므로 가격·추세선 알림이 5분간 한 번도 판정되지 않는다.
고치는 방향 tick 당 사다리를 한 번만 올린다(hitLimitThisTick을 이미 들고 있다). 시세 판정만은 물러섬에서 빼는 것도 검토.
근거 server/exchanges.ts:140-145 — fetchTickersByVenue는 전부 실패했을 때만 던진다. 일부 실패는 로그만 남기고 부분 결과를 돌려준다.
왜 문제인가 바이비트만 한도에 걸리고 바이낸스는 멀쩡하면: 바이비트 심볼 알림은 가격이 없어 판정을 건너뛰고, 물러서지도 않으니 15초마다 계속 두들기고, 화면은 “감시 정상”이라고 말한다. 오늘 만든 stalled 표시가 정확히 이 경우에 안 먹는다.
고치는 방향 실패한 묶음 목록을 결과에 실어 돌려주고, 한도 때문이면 호출부가 백오프·stalled에 반영한다(어느 거래소인지까지).
근거 useBars.ts:204가 to = 마지막봉 + 1000 × 인터벌로 요청하는데, 합성 인터벌은 기반 봉 1000개만 돌아와 실제 창이 훨씬 짧다. pagination.ts:77의 appendPage는 “마지막보다 뒤”만 보고 연속성을 안 본다.
왜 문제인가 8시간봉 리플레이에서 한 장을 더 받는 순간 167일이 통째로 사라진다(3일봉은 더 크다). 캔들은 매끄럽게 이어져 보여서 눈으로는 절대 못 잡는다. 그 위에서 거래 연습을 하면 결과 전체가 거짓말이 된다. 네이티브 인터벌은 정확히 이어지므로 합성에서만 난다.
고치는 방향 appendPage가 새 페이지 첫 봉이 한 인터벌 넘게 떨어져 있으면 붙이지 않고 그 사실을 돌려준다. 조용한 구멍보다 “여기까지”가 낫다.
근거 server/main.ts 814줄 중 53~809줄이 main() 하나. main.test.ts 머리말이 직접 적는다 — “전부 main() 클로저 안에 있고, 그 파일을 import하면 즉시 서버가 뜬다”.
왜 문제인가 위 P0 중 셋이 이 구역 안이다(백오프 99-108, ntfy 순서 254-257, 부분 실패 처리). 우연이 아니다. M18 회고가 이미 같은 말을 적어 뒀다 — “불변식 하나에 좋은 테스트를 만든 것을 ‘테스트를 했다’로 착각했다. 결함 ①②③④⑦이 전부 그 빈 곳에 살았다.” 백로그에 08-30 최초 기록 → 08-31 재기술 → 09-16 또 기록, 17일 무변화. 이 레포에서 가장 오래 열려 있는 단일 항목이다.
고치는 방향 이미 절반은 했다 — 순수 판정을 밖으로 뺀 dueSquirrelIntervals·backoffAfter·isRateLimited가 그것이다. 남은 한 걸음은 백로그가 09-01에 이미 적어 둔 문장이다: “서버가 거래소 주소를 주입받을 수 있어야 한다.” 그러면 tickPrices·tickSignals를 박제로 돌릴 수 있고, 위 셋에 회귀 테스트가 붙는다.
사용자가 체감하거나, 언젠가 반드시 물게 되는 것들. 대부분 S다.
| 항목 | 근거 | 왜 | 크기 |
|---|---|---|---|
| 재시작하면 ABC 목표선이 전멸 | main.ts:67-76 | 메모리 전용이라 다음 봉이 닫힐 때까지 실명 — 주봉이면 최대 7일. 화면·stalled 모두 정상이라 완벽하게 조용하다. | M |
| 원장 500칸이 주봉보다 짧다 | ledger.ts:7-9 | 실측 하루 105건 → 4.8일치. 바쁜 주엔 월요일 주봉 키가 금요일에 밀려 나가 재시작 후 중복 푸시. 나이 기반(40일)으로 바꾸면 끝. | S |
| 봉이 0개면 스로틀을 안 세워 영구 재조회 | main.ts:308 | continue가 스로틀 갱신보다 앞이라, 역사가 짧은 쌍은 15초마다 영원히 조회된다. 알림은 안 울리고 한도만 태운다. | S |
| 옛 커서가 인터벌 전환마다 다시 풀린다 | useReplay.ts:65-69 | 신고했던 “타임프레임 바꾸면 미래 캔들”이 마이그레이션 안 끝난 판에 그대로 남아 있다. 그 자리에서 체결까지 된다. | M |
| 월봉 카운트다운이 30일 고정 | ChartView.tsx:1758 | 같은 레포가 “그렇게 셈하면 31일 달에서 깨진다”고 경고해 둔 계산을 카운트다운만 쓴다. 1월엔 하루 동안 00:00에 박힌다. | S |
| 마우스만 움직여도 MTF 지표 전량 재계산 | useMtfBars.ts:67-72 | 반환 객체에 useMemo가 없어 다섯 개 메모가 프레임마다 깨진다. 구름대·RSI 표를 켠 데스크톱에서 즉시 체감. | S |
| 다람쥐 신호를 렌더마다 40~64번 다시 계산 | useSquirrelStreams.ts:67-114 | 메모가 하나도 없다. 4× 재생이면 초당 25만 회 루프 — 폰에서 재생이 끊기는 주된 자리. | S |
| 번들이 압축 없이 나간다 | deploy/nginx.conf | 실측 644KB → gzip 203KB · brotli 173KB. LTE 첫 로딩 1.03초 → 0.28초. nginx 세 줄. | S |
| 수량 한 글자마다 지표 메모 전체 무효화 | usePersisted.ts:40 | read()가 매번 새 객체를 만들어 opt.* 의존 메모가 다 깨진다. 가림 토글·지표 옵션도 같다. | M |
| 리플레이 한 걸음마다 저장소 전체 직렬화 | useLayouts.ts:50-62 | setItem은 동기 디스크 쓰기다. 4× 재생 초당 4회 — 폰 끊김의 두 번째 원인. 디바운스면 해결. | M |
| 🔔과 ⍾의 접근성 이름이 글자까지 같다 | SquirrelAlertPopover.tsx:94 | “알림”이라는 낱말이 여섯 곳에서 다른 것을 가리킨다. “알림 걸기 / 알림 목록”으로 갈라 적으면 끝. | S |
| “범위”가 두 곳에서 다른 것을 조종 | options.ts · SignalAlertSetup.tsx:151 | 지표 옵션은 그리는 문턱, 🔔은 울리는 문턱인데 라벨이 같다. 차트엔 71개가 그려지고 알림은 9개 기준으로 운다. | S |
| 리플레이에 나가는 길이 없다 | ReplayBar.tsx | 들어가는 길도 시트 속 ▶ 하나였고(“유아이가 안보이는데?”), 끄려면 다시 그 줄을 찾아야 한다. 막대 끝에 ✕ 하나. | S |
| 알림 삭제만 한 번에 끝난다 | AlertList.tsx:105,126 | 그림·판·계좌는 전부 두 번 눌러 확정인데, 되돌릴 수 없는 서버 레코드만 한 번이다. 가장 큰 과녁이 가장 위험한 자리에 있다. | S |
| RSI 표가 세로 알약 뒤에 깔린다 | RsiTable.tsx:53-61 | bottom:82의 근거가 가로 알약 시절 값이다. 지금은 40×188이라 폰에서 표 머리 칸이 가린다. | S |
그림 앵커만 Infinity를 통과시킨다 | schema.ts:236-238 | 알림 쪽은 Number.isFinite로 막아 두고 이유까지 적었는데, 정작 저장 왕복이 잦은 그림 쪽이 열려 있다. 저장되면 null로 굳어 그림 전체가 버려진다. | S |
constructor 키가 임베드 저장소를 초기화시킨다 | schema.ts:527 | __proto__만 막는다. 레이아웃 저장소는 같은 함정을 이름까지 적어 두고 Object.hasOwn으로 막았는데, 이쪽만 안 막혔다. | S |
| 항목 | 왜 지금은 아닌가 / 언제 물게 되나 | 크기 |
|---|---|---|
version을 2로 올릴 길이 없다 | 올리는 순간 몇 년치 그림과 모든 판·계좌가 사라진다. 게다가 양방향이라 롤아웃 중 두 탭이 서로를 지운다. 모양을 바꿔야 하는 날이 오기 전에 탈출로(순서 있는 업캐스터 또는 새 키)를 설계해 둔다. | M |
| 지운 알림이 파일에 되살아나는 경합 | DELETE의 await가 열린 사이 tick 저장이 끼면 메모리엔 없고 파일엔 있는 상태가 된다. 지금 실측은 일치하지만 구조적으로 가능하다. | M |
| tick이 직렬이라 15초가 최악 150초로 | 거래소가 죽지 않고 느려지기만 해도 가격 알림 샘플링이 몇 분이 된다. stalled는 한도에만 반응하므로 화면은 정상이라 말한다. | M |
findAbc가 피봇마다 배열을 잘라 지그재그를 다시 돈다 | 20,000봉이면 틱마다 슬라이스 1,000개. 지표를 전부 켜고 과거를 당겨 둔 화면은 과거 성능 측정이 한 번도 안 덮은 구간이다. | L |
죽은 datafeed/bybit.ts·ws.ts가 두 번째 접기 구현으로 살아 있다 | 주석은 “UI가 아는 유일한 출처”라고 말하지만 프로덕션 참조 0건. 규칙을 고칠 때 한 벌만 고쳐도 죽은 쪽 테스트가 초록이라 안 들킨다. | S |
| 다람쥐 8스트림만 어댑터를 안 탄다 | “거래소별 상한은 어댑터 안에서 끝난다”는 계약 밖에 있다. 3일봉 스트림은 지금도 333봉만 본다. | M |
| 라이브 시세를 그대로 쓰는 테스트 7개 | 난이도 문제가 아니라 잔여 작업이다 — 픽스처 생성기 일가(fvgSeries·bandSeries·abcSeries·riseFallSeries)와 규칙이 이미 다 서 있는데 남은 7개에 안 발랐을 뿐. | M |
| 폰에서 차트 설정에 닿는 길이 두 손가락 탭뿐 | 거래량·그리드·현재가 선·다음 그림 기본값이 폰에서 사실상 존재하지 않는다. 그 손짓을 적은 글자가 앱 어디에도 없다. | M |
| 가장 자주 누르는 줄만 40×40이 아니다 | 인터벌 칩·TF 칩이 22~26px. 이 앱에서 제일 많이 눌리는 줄이다. | S |
결함 목록과 별개로 물어야 하는 질문이다 — 초록불이 초록불인가. 백로그 5,300줄을 집계한 결과다.
플레이크 언급 16회 중 근본원인까지 간 것 5 · 재시도로 넘긴 것 7 · 미해결로 남긴 것 4. 32849a2 이전 풀 실행은 188·187·188·188 — 실행마다 자리를 바꾸는 빨강 하나였고, 고친 뒤 188/188 3연속이다. 남은 넷(smoke:1554·smoke:1916·smoke:389·layouts:523)에 대해 레포는 “고쳤다고 말하지 않는다”고 정직하게 적었다.
⚠️ 다만 재시도 습관은 고친 지 4일 만에 재발했다(같은 mobileDraw 조준-배치 경로). 레포가 그 습관을 “모든 변경의 관문을 못 믿게 만든다”고 지목해 놓고도 아직 못 잡은 자리다.
각각 20회·25회 이상. 뒤엣것은 레포의 자기 카운터가 망가져 있다 — 네 번째 → 열 번째 → 열한 번째(서로 다른 둘이 같은 번호) → 열두 번 → 다시 “네 번”. 아무도 안 세고 있으니 레포가 아는 것보다 크다.
더 중요한 건 발견 경로가 거의 전부 사람이라는 점이다(최종 리뷰·적대적 리뷰·사용자 신고). 불변식을 실제로 지키는 테스트는 signalAlertSpecs.test.ts의 “home — 알림이 사는 곳” 딱 하나다. 중복을 합칠 때마다 그 한 줄을 같이 복제하는 것이 이 카테고리를 끝내는 유일한 길이다.
앞선 규율도 이미 글로 서 있다 — “Tailwind를 쓰는 한 클래스 문자열로 상태를 판정하면 안 된다”(3번 당한 뒤 규칙이 됨), 그리고 한 단계 위: “대조군 0을 확인하는 것으로는 부족하다 — 증명하려는 것만 끄면 빨개지는가를 재야 한다.”
전부 “점검 안 했다”가 본문에 박힌 항목이다. 기록은 정직한데 후속이 없다.
· 다른 right-0 팝오버에 같은 함정이 있는지 미점검(3,800줄 지나도록 재방문 없음)
· ChartView.tsx 가격축 + 단추에 같은 transition-transform 함정을 남겨둔 채 기록
· 통합이 새 구멍을 만든 사례 — 목록은 RSI_LEVEL_ALERT_TF_CHOICES 한 곳인데 단정이 네 곳이라, 칩을 더하면 새 칩이 검증 밖에 남는다
· M16 이중 방어선에 가드가 없다 — “‘중복이니 하나 지우자’는 미래의 정리가 마지막 방어선을 조용히 없앨 수 있다”
검토가 결함 목록만 되면 무엇을 지켜야 하는지가 안 보인다. 이 여섯은 근거와 함께 확인됐다.
barCloseTime 하나를 경계 필터·알림 서버·신호 스로틀·리플레이 커서·날짜 고르기·마커 배치가 전부 부른다. 1M을 달력으로 셈하는 것도 거기 한 곳이다. 이탈자는 월봉 카운트다운 하나뿐이었다.
quant-trade의 벤더 사본은 9월 3일자인데, 그 뒤 M49·M50과 리플레이 전부가 그 사본을 한 글자도 안 건드리고 지나갔다. 임베드에서 심볼 키 마이그레이션을 끈 판단도 같은 결로 맞았다.
tmp + rename + 경로별 프로미스 체인. 알림을 하루 수십 번 만졌는데 /data에 .tmp·.bad 잔해가 0개. “200회 중 26회 파손”이던 옛 결함이 확실히 잡혔다.
“마지막 봉에서 확정된 것만” + 파일에 남는 원장. 오늘 컨테이너를 서른 번 가까이 재생성했는데 중복 푸시가 없었던 이유다. 미지원 소스를 조용한 [] 대신 throw로 막은 것이 이 축에서 가장 값진 선택이다.
바이낸스·게이트가 8시간·3일봉을 네이티브로 줘도 안 받는다. 두 거래소의 마커가 같은 줄에 앉는 것이 우연이 아니라 구조다. 실시간 합성 봉의 누적 거래량 함정도 세 겹으로 막혀 있다.
지표 패널·그림 툴바·🔔이 모두 같은 규칙을 따른다. 날짜까지 판과 임베드에서 “켰는데 안 온다”가 구조적으로 불가능하다. 실패를 말하는 알약이 스스로 사라지지 않는 것도 같은 철학이다.
에이전트가 git checkout --로 자기 구현을 통째로 날린 사고가 두 번 있었고, 그 뒤 대조군 복원은 스크래치패드 cp + diff/shasum 바이트 동일성 확인이 표준이 됐다. 변이 테스트도 두 갈래로 정착했다 — 이전 커밋 워크트리는 “이 기능이 정말 새로운가”, 사보타주는 “이 줄이 정말 그 테스트를 지키는가”.