이 글에서 다루는 것
하루에 다섯 갈래 기술 작업을 다뤘다. 개인 블로그의 문서 관계 그래프에 Canvas 별빛 노드를 넣었고, 다른 제품에서는 Playwright CI 로그를 전수 조사해 flaky와 deterministic failed를 분리했다. 같은 맥락에서 Service Worker 이미지 캐시(stale-while-revalidate + byte-LRU)를 만들고, 마스킹·로그아웃 때 generation guard로 원본 재노출을 막았다. 디자인 시스템 쪽은 MCP만으로는 부족해 ESLint guardrail 플러그인을 추가했다.
각 작업은 배경지식 → 어떤 상황 → 핵심 작업 → 교훈 순서로 묶는다.
하루 요약
| 작업 | 무엇을 하고 싶었나 | 한 일 | 결과 |
|---|---|---|---|
| 1. 별빛 그래프 노드 | 관계 그래프가 차트 점처럼 보임 | ID 해시 기반 Canvas 색·반짝임·shadow | 테스트·VR 통과 |
| 2. Playwright CI triage | 빨간 CI가 flaky인지 버그인지 | 392건 로그 전수 분류 | flaky 36 vs failed 분리 |
| 3. SW 이미지 SWR | 이미지 재조회마다 네트워크 대기 | stale-while-revalidate + 100MB LRU | 단위·E2E green |
| 4. 마스킹 캐시 경계 | 마스킹 후 원본 캐시 재노출 | 삭제 ACK → UI 갱신 + generation | 보안 순서 확립 |
| 5. DS ESLint guardrail | AI가 native UI를 재구현 | prefer-ds-component·no-reimplement | 소비자 repo 검증 |
1. Canvas 별빛(stellar) 노드
배경지식 (개념)
- Canvas 2D: 브라우저가 픽셀 버퍼에 직접 그리는 API. DOM이 아니어서 CSS 애니메이션을 노드에 직접 걸 수 없다.
- force-directed graph: 노드 간 인력·반발력으로 배치를 잡는 그래프 시각화. 보통 Canvas 위에 그린다.
- prefers-reduced-motion: OS·브라우저의 "애니메이션 줄이기" 설정.
어떤 상황이었나
- Knowledge·TIL 상세의 로컬 관계 그래프 노드가 주황 점처럼 보여 밤하늘(observatory) 톤과 맞지 않았다.
- 패널 배경 CSS 별은 반짝이지만 문서 노드는 Canvas 안에 있어 같은 방식을 쓸 수 없었다.
핵심 작업
- 문서 ID 해시로 색(적백·백·청백)·반짝임 주기·위상을 안정적으로 계산.
- draw 콜백에서
globalAlpha·shadowBlur적용 후 상태 복구. autoPauseRedraw={reducedMotion}으로 모션 감소 시 반짝임 중지.- 범례는 중립색 — 노드 색이 문서 종류를 뜻하지 않게.
교훈
- Canvas는 그리기 상태가 다음 프레임까지 남는다 — alpha·shadow는 반드시 리셋.
- visual 메타는 attach 시 한 번, 프레임마다는 시간 기반 alpha만.
→ Canvas deterministic node rendering
2. Playwright flaky vs failed
배경지식 (개념)
- flaky: 첫 실행 실패 → retry 성공.
- deterministic failed: 모든 retry가 같은 이유로 실패.
- CDP: 브라우저 예외의 URL·줄 번호를 읽는 진단 프로토콜.
어떤 상황이었나
- E2E CI가 불안정해 보였고, 특정 스펙이 flaky인지 매번 깨지는지 구분이 필요했다.
핵심 작업
- 수개월치 push 워크플로 로그 전수 분석 → flaky 36건·8 테스트.
- 100% 동일 SyntaxError는 custom webpack
splitChunks가 CSS를 JS entry에 넣은 빌드 결함으로 확인. - SSE 타이머·Radix portal focus·
networkidle·느슨한waitForRequest가 flaky 상위 원인.
교훈
- CI conclusion만으로 flaky 판정 금지 — retry 결과를 본다.
- production build + CDP로 deterministic failed를 재현한다.
→ Playwright flaky vs failed triage · Webpack splitChunks trap
3. Service Worker stale-while-revalidate
배경지식 (개념)
- Service Worker: fetch를 가로채 Cache Storage에 응답을 저장하는 브라우저 스크립트.
- stale-while-revalidate: 캐시 즉시 반환 + 백그라운드 최신화.
- presigned URL: 만료 서명이 붙은 임시 다운로드 주소.
어떤 상황이었나
- 클라우드 스토리지 이미지를 다시 열 때마다 네트워크를 기다려야 했다.
핵심 작업
- 캐시 hit 시 즉시 반환, miss만 await.
- 인증 쿼리만 캐시 키에서 제거,
masked=1등 의미 쿼리 유지. - 100MB byte-LRU(본문 + 접근 메타).
교훈
- presigned 캐시는 "쿼리 전체 제거"가 아니라 "서명만 strip".
- byte LRU는 본문·메타를 같이 움직인다.
→ Service Worker stale-while-revalidate · Presigned URL cache key normalization
4. 마스킹·로그아웃 캐시 보안
배경지식 (개념)
- SWR은 성능 패턴이지만 마스킹 직후에는 원본 재노출이 보안 문제다.
- race condition: 삭제 중 in-flight fetch가 원본을 다시 저장할 수 있다.
- generation: 삭제마다 올리는 세대 번호로 저장 자격을 판별.
어떤 상황이었나
- 마스킹 API 성공 후에도 브라우저 캐시 원본이 잠깐 보일 수 있었다.
핵심 작업
- 마스킹 성공 → 캐시 삭제 ACK 대기 → UI 데이터 갱신.
- generation guard로 삭제 전 시작된 revalidate의
put차단. - 로그아웃 두 경로 모두 이미지 캐시 전체 삭제.
교훈
- 민감 데이터는 "지우기"와 "진행 중 요청 저장 취소"가 별개다.
- 보안 경계에서는 ACK가 UI 갱신보다 앞이다.
→ Cache generation stale-write guard
5. 디자인 시스템 ESLint guardrail
배경지식 (개념)
- ESLint 플러그인: 코드 패턴 위반을 정적 분석으로 리포트.
- MCP: AI 에이전트가 외부 문서·도구에 접근하는 표준 — DS 문서를 "push"로 제공.
- guardrail은 MCP(push)와 ESLint(catch)가 상호 보완.
어떤 상황이었나
- AI·개발자가 native
<button>·로컬 Modal을 다시 만들어 DS를 우회했다. - hex·spacing 하드코딩은 허용, "DS 미사용"만 잡고 싶었다.
핵심 작업
prefer-ds-component,no-reimplement두 규칙.- 메시지에 import 경로·MCP 툴 힌트(agent-cognizable).
- 워크스페이스·per-package CI publish 정비.
- 플러그인은 소비자 repo만 — DS 자체 소스에는 미적용.
교훈
- DS guardrail은 "값 금지"보다 "컴포넌트 우회 금지"가 실용적.
- publishable pkg 추가 시 lockfile + CI publish job을 함께 확인.