Skip to content

Commit 9f9fee6

Browse files
sonwrclaude
andcommitted
docs: update PROJECT_STATUS with detailed session work log
Document all work performed in 2026-02-23 session: - ConversationBlock chat bubble layout redesign - CSS 400 Bad Request diagnosis and fix - WebSocket exponential backoff implementation - React.memo on 4 chronicle block components - Store fetch error handling with logging - Production deployment verification results - Identified future improvement backlog (8 items) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f5e0fa0 commit 9f9fee6

1 file changed

Lines changed: 222 additions & 13 deletions

File tree

docs/PROJECT_STATUS.md

Lines changed: 222 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -311,26 +311,218 @@
311311

312312
## 9. Recent Changes (최근 변경 사항)
313313

314-
### 2026-02-23: Chronicle ConversationBlock 가독성 개선
314+
### 2026-02-23 세션 작업 로그
315315

316-
**파일:** `frontend/src/components/chronicle/ConversationBlock.tsx`
316+
이 섹션은 2026-02-23 세션에서 수행한 모든 작업을 시간순으로 상세 기록한다.
317+
318+
---
319+
320+
#### 작업 1: Chronicle ConversationBlock 가독성 개선
321+
322+
**커밋:** `78b8348``feat: improve Chronicle ConversationBlock readability and add project status doc`
323+
324+
**문제 분석:**
325+
- 에이전트 이름과 대화 내용이 한 줄에 `flex items-start`로 나열 → 시각적 밀도가 높아 읽기 어려움
326+
- 메시지 간 간격 `space-y-1.5`이 너무 좁아 경계가 불분명
327+
- 팩션 색상 좌측 보더 불투명도 `30` → 거의 안 보임
328+
- LLM 생성 장문 텍스트(줄바꿈, `*이탤릭*` 등)가 한 줄짜리 `<span>`에 담겨 서식 무시
329+
330+
**변경 파일 및 내용:**
331+
332+
| 파일 | 변경 |
333+
|------|------|
334+
| `frontend/src/components/chronicle/ConversationBlock.tsx` | 채팅 버블 레이아웃으로 전면 개편 |
335+
| `frontend/src/stores/simulation.ts` | `loadChronicleFromDB()` 함수 추가 |
336+
| `frontend/src/components/chronicle/ChronicleView.tsx` | `AnimatePresence` 래퍼 제거 |
337+
| `frontend/src/components/divine/InterventionBar.tsx` | Whisper API 바디 필드명 수정 |
338+
| `docs/PROJECT_STATUS.md` | 프로젝트 현황 종합 문서 신규 작성 |
339+
340+
**ConversationBlock 상세 변경:**
341+
342+
```
343+
Before After
344+
───────────────────────────── ─────────────────────────────
345+
flex items-start (한 줄) → 에이전트 이름 별도 줄 (상단)
346+
메시지 내용 블록 (하단)
347+
border opacity 30 → border opacity 80
348+
<span> (서식 무시) → <p whitespace-pre-wrap> + *italic* → <em>
349+
space-y-1.5 → space-y-4
350+
text-[11px] font-mono (토픽) → text-sm font-serif
351+
slice(0, 2) (기본 2개 표시) → slice(0, 3) (기본 3개 표시)
352+
bg 없음 → bg-void/50 블록 배경
353+
```
354+
355+
**`renderContent()` 함수 추가:**
356+
- `*text*` 패턴을 `<em>text</em>`으로 변환
357+
- LLM이 자주 사용하는 이탤릭 마크다운을 실제 HTML로 렌더링
358+
359+
**`loadChronicleFromDB()` 함수 추가:**
360+
- 월드 진입 시 DB에서 기존 대화 50개 + 위키 페이지를 Chronicle로 로드
361+
- `conversations` API와 `wiki` API를 `Promise.all`로 병렬 호출
362+
- WebSocket으로 수신한 기존 아이템과 ID 기반 중복 제거 후 병합
363+
- 타임스탬프 역순 정렬 (최신 항목 상단)
364+
365+
**검증:** `npm run build` 성공
366+
367+
---
368+
369+
#### 작업 2: CSS 400 Bad Request 이슈 대응
370+
371+
**증상:**
372+
- `https://null.moss.land/en` 접속 시 `_next/static/css/cb96dbb2f870df51.css` → 400 Bad Request
373+
- JS 파일도 동일하게 400 에러 발생
374+
375+
**원인 분석:**
376+
- 빌드 출력의 CSS 해시: `9ecfc2f30213e6fa`
377+
- 브라우저가 요청하는 CSS 해시: `cb96dbb2f870df51` (이전 빌드)
378+
- HTML이 브라우저/CDN에 캐시되어 있어 이전 빌드의 해시를 참조
379+
- 서버에는 새 빌드 파일만 존재 → 404/400 반환
380+
381+
**해결:**
382+
1. `npm run build` 실행 → 새 CSS 해시 생성
383+
2. `pm2 restart null-frontend` → 새 빌드 파일 서빙 시작
384+
3. 브라우저 강제 새로고침 (`Cmd+Shift+R`) 안내
385+
386+
**검증:** `curl -s -o /dev/null -w "%{http_code}" https://null.moss.land/_next/static/css/9ecfc2f30213e6fa.css` → 200 OK
387+
388+
**참고:** 향후 Nginx에서 HTML의 `Cache-Control: no-cache` 설정 권장 (정적 에셋은 해시 기반이므로 장기 캐시 가능)
389+
390+
---
391+
392+
#### 작업 3: WebSocket 재연결 강화
393+
394+
**커밋:** `4436387``fix: add exponential backoff and retry limit to WebSocket reconnection`
395+
396+
**문제 분석 (`frontend/src/lib/wsClient.ts`):**
397+
398+
| 문제 | 위험도 |
399+
|------|--------|
400+
| 3초 고정 재연결 간격 | 서버 다운 시 3초마다 무한 폭격 |
401+
| 최대 재시도 제한 없음 | 클라이언트 리소스 무한 소모 |
402+
| `onerror`에서 에러 상세 무시 | 디버깅 불가능 |
403+
| `onmessage` parse 에러 무시 | 프로토콜 오류 탐지 불가 |
404+
| `onclose``wsRef` 미초기화 | stale reference 위험 |
405+
| `disconnect` 시 retry 카운터 미리셋 | 재접속 시 즉시 한도 도달 |
317406

318407
**변경 내용:**
319408

320-
1. **채팅 버블 레이아웃** — 에이전트 이름을 별도 줄로 분리하고, 메시지를 `bg-void/50` 블록으로 감싸 시각적 분리
321-
2. **보더 가시성 강화** — 팩션 색상 좌측 보더 불투명도 `30``80`
322-
3. **서식 렌더링**`whitespace-pre-wrap`으로 줄바꿈 지원, `*text*``<em>` 이탤릭 변환
323-
4. **간격 확대** — 메시지 간 간격 `space-y-1.5``space-y-4`
324-
5. **토픽 헤더**`text-[11px] font-mono``text-sm font-serif`로 확대
325-
6. **기본 메시지 수** — 접힌 상태에서 2개 → 3개 표시
409+
```typescript
410+
// Before
411+
ws.onclose = () => {
412+
reconnectTimer.current = setTimeout(() => connect(worldId), 3000);
413+
};
414+
415+
// After
416+
const MAX_RETRIES = 10;
417+
const BASE_DELAY_MS = 2_000;
418+
const MAX_DELAY_MS = 30_000;
419+
420+
ws.onopen = () => { retryCount.current = 0; };
421+
422+
ws.onclose = (event) => {
423+
wsRef.current = null;
424+
if (retryCount.current >= MAX_RETRIES) {
425+
console.error(`[WS] Gave up after ${MAX_RETRIES} attempts`);
426+
return;
427+
}
428+
const delay = Math.min(BASE_DELAY_MS * Math.pow(2, retryCount.current), MAX_DELAY_MS);
429+
retryCount.current += 1;
430+
console.log(`[WS] Reconnecting in ${delay}ms (${retryCount.current}/${MAX_RETRIES})`);
431+
reconnectTimer.current = setTimeout(() => connect(worldId), delay);
432+
};
433+
```
326434

327-
### 2026-02-23: Chronicle DB 로딩 + 기타 수정
435+
**재연결 지연 시퀀스:** 2s → 4s → 8s → 16s → 30s → 30s → ... (최대 10회)
328436

329-
**파일들:**
437+
**검증:** `npm run build` 성공, `git push` 성공
330438

331-
- `frontend/src/stores/simulation.ts``loadChronicleFromDB()` 추가: 월드 진입 시 DB에서 기존 대화/위키를 Chronicle로 로드, WebSocket 아이템과 중복 제거 후 병합
332-
- `frontend/src/components/chronicle/ChronicleView.tsx``AnimatePresence` 래퍼 제거 (불필요한 레이아웃 애니메이션 오버헤드 제거)
333-
- `frontend/src/components/divine/InterventionBar.tsx` — Whisper API 바디 필드명 수정 (`message``type` + `description`)
439+
---
440+
441+
#### 작업 4: Chronicle 블록 React.memo 적용 (성능 최적화)
442+
443+
**커밋:** `f5e0fa0``perf: memoize chronicle blocks and add error logging to store fetches`
444+
445+
**문제 분석:**
446+
- Chronicle은 최대 500개 아이템을 렌더링
447+
- 4개 블록 컴포넌트(`ConversationBlock`, `HeraldBlock`, `EventBlock`, `WikiCrystal`)가 모두 일반 함수 컴포넌트
448+
- 부모(`ChronicleView`) state 변경 시 모든 자식이 re-render → props 미변경인 아이템도 불필요하게 렌더링
449+
450+
**변경 내용:**
451+
452+
| 파일 | Before | After |
453+
|------|--------|-------|
454+
| `ConversationBlock.tsx` | `export function ConversationBlock(...)` | `export const ConversationBlock = memo(function ConversationBlock(...))` |
455+
| `HeraldBlock.tsx` | `export function HeraldBlock(...)` | `export const HeraldBlock = memo(function HeraldBlock(...))` |
456+
| `EventBlock.tsx` | `export function EventBlock(...)` | `export const EventBlock = memo(function EventBlock(...))` |
457+
| `WikiCrystal.tsx` | `export function WikiCrystal(...)` | `export const WikiCrystal = memo(function WikiCrystal(...))` |
458+
459+
**효과:**
460+
- props(item, dimmed, onAgentClick)가 동일한 아이템은 re-render 스킵
461+
- 500개 목록에서 1개 아이템 추가 시: Before → 500개 전체 렌더, After → 신규 1개만 렌더
462+
- `WikiCrystal`은 내부 `useState(expanded)`가 있지만 memo가 외부 props 변경만 체크하므로 유효
463+
464+
---
465+
466+
#### 작업 5: Store Fetch 에러 처리 개선
467+
468+
**커밋:** `f5e0fa0` (작업 4와 동일 커밋)
469+
470+
**문제 분석:**
471+
- `simulation.ts` 내 12개 fetch 함수에서 에러를 무시하는 패턴이 반복:
472+
```typescript
473+
catch {
474+
// endpoint may not exist yet ← 모든 에러를 무시
475+
}
476+
```
477+
- `createWorld`, `fetchWorld`, `fetchAgents` 등 핵심 함수에 try/catch 자체가 없음
478+
- 네트워크 장애, 서버 500 에러, JSON 파싱 실패 등이 모두 무시됨
479+
- 프로덕션에서 문제 발생 시 디버깅 불가능
480+
481+
**변경 내용:**
482+
483+
| 함수 | Before | After |
484+
|------|--------|-------|
485+
| `createWorld` | try/catch 없음 | `try/catch` + `console.error("[Store] createWorld error:", err)` |
486+
| `fetchWorld` | try/catch 없음 | `try/catch` + `resp.ok` 체크 + `console.error` |
487+
| `fetchAgents` | try/catch 없음 | `try/catch` + `resp.ok` 체크 + `console.warn` |
488+
| `fetchFactions` | `catch { }` (무시) | `catch (err) { console.warn("[Store] fetchFactions:", err) }` |
489+
| `fetchRelationships` | `catch { }` (무시) | `catch (err) { console.warn("[Store] fetchRelationships:", err) }` |
490+
| `fetchWikiPages` | `catch { }` (무시) | `catch (err) { console.warn("[Store] fetchWikiPages:", err) }` |
491+
| `fetchAutoWorlds` | `catch { }` (무시) | `catch (err) { console.warn(...) }` + `resp.ok` 체크 |
492+
| `startSimulation` | try/catch 없음 | `try/catch` + `resp.ok` 체크 |
493+
| `stopSimulation` | try/catch 없음 | `try/catch` + `resp.ok` 체크 |
494+
| `fetchConversations` | `catch { }` (무시) | `catch (err) { console.warn(...) }` |
495+
| `fetchFeed` | `catch { }` (무시) | `catch (err) { console.warn(...) }` |
496+
| `exportWorld` | try/catch 없음 | `try/catch` + `resp.ok` 체크 + early return |
497+
498+
**로깅 규칙:**
499+
- `console.error` — 사용자 액션이 실패한 경우 (createWorld, startSimulation, stopSimulation, exportWorld, fetchWorld)
500+
- `console.warn` — 백그라운드/보조 데이터 로드 실패 (factions, relationships, wiki 등)
501+
- 모든 로그에 `[Store] functionName:` 프리픽스 → 브라우저 콘솔에서 빠른 필터링
502+
503+
---
504+
505+
#### 작업 6: 프로덕션 배포 검증
506+
507+
**검증 항목 및 결과:**
508+
509+
| 항목 | 방법 | 결과 |
510+
|------|------|------|
511+
| PM2 프로세스 상태 | `pm2 list` | null-backend: online (5h), null-frontend: online (2h) |
512+
| 프론트엔드 헬스 | `curl localhost:6001/en` | 200 OK |
513+
| 백엔드 헬스 | `curl localhost:6301/health` | `{"status":"ok"}` |
514+
| 도메인 프론트엔드 | `curl https://null.moss.land/en` | 200 OK |
515+
| 도메인 API | `curl https://null.moss.land/api/worlds` | 200 OK (9개 월드, 5개 running) |
516+
| CSS 파일 정상 로드 | `curl https://null.moss.land/_next/static/css/9ecfc2f30213e6fa.css` | 200 OK |
517+
| 백엔드 서비스 로그 | `pm2 logs null-backend` | semantic_indexer, convergence 정상 주기 실행 |
518+
| WebSocket 연결 | 백엔드 로그 확인 | WS open/close 정상 사이클 |
519+
520+
**발견 사항:**
521+
- 백엔드 실제 포트는 `6301` (start-null-backend.sh의 `NULL_BACKEND_PORT` 기본값)
522+
- Docker Compose 설정의 `3301`은 컨테이너 내부 포트, PM2 직접 실행 시 `6301` 사용
523+
- 리버스 프록시(Nginx/Caddy)가 `null.moss.land``localhost:6301` 프록시 중
524+
525+
---
334526

335527
### 이전 주요 커밋
336528

@@ -343,6 +535,23 @@
343535

344536
---
345537

538+
### 향후 개선 과제 (분석 완료, 미착수)
539+
540+
아래 항목들은 코드 분석 과정에서 식별된 개선 사항으로, 다음 작업 세션에서 우선순위에 따라 진행 예정:
541+
542+
| # | 과제 | 카테고리 | 예상 시간 |
543+
|---|------|----------|-----------|
544+
| 1 | 홈페이지 `/api/seeds` fetch에 에러 핸들링 추가 | 에러 처리 | 10분 |
545+
| 2 | `OraclePanel` index-based key → timestamp key | 성능 | 5분 |
546+
| 3 | `ChronicleView.isDimmed` 메모이제이션 | 성능 | 20분 |
547+
| 4 | `OraclePanel`, `ExportPanel`, `BookmarkDrawer` lazy loading (`dynamic()`) | 번들 최적화 | 30분 |
548+
| 5 | 주요 버튼에 `aria-label` 추가 (접근성) | 접근성 | 15분 |
549+
| 6 | `addChronicleItem`의 배열 spread + slice → circular buffer | 성능 | 30분 |
550+
| 7 | `TimelineRibbon``getEpochColor` O(n) 검색 → Map 인덱싱 | 성능 | 15분 |
551+
| 8 | 네트워크 연결 상태 감지 UI (`navigator.onLine`) | UX | 30분 |
552+
553+
---
554+
346555
## 10. Key Concepts (핵심 개념 정리)
347556

348557
### Simulation Concepts

0 commit comments

Comments
 (0)