|
| 1 | +# Viz Tab Data-Driven Visibility Plan |
| 2 | +Date: 2026-03-05 |
| 3 | + |
| 4 | +## 목표 |
| 5 | + |
| 6 | +LessonUnifiedView의 시각화 탭(Flow / Memory / JS Memory)을 |
| 7 | +언어 하드코딩이 아닌 **실제 데이터 유무**로 노출 여부를 결정한다. |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## 현황 및 문제점 |
| 12 | + |
| 13 | +```ts |
| 14 | +// LessonUnifiedView.tsx:78-80 (현재) |
| 15 | +const showMemoryTab = languageId === 'c' || languageId === 'cpp'; // 언어만 봄 |
| 16 | +const showJsMemoryTab = languageId === 'javascript'; // 언어만 봄 |
| 17 | +``` |
| 18 | + |
| 19 | +- C/C++ 레슨이라도 메모리 데이터가 없으면 Memory 탭이 떠서 텅 빔 |
| 20 | +- JS 레슨이라도 eventLoop/scope만 있는 레슨(js-1-3 등)에 JS Memory 탭이 불필요하게 노출됨 |
| 21 | +- 탭이 1개뿐일 때도 탭 바 UI가 표시됨 |
| 22 | + |
| 23 | +--- |
| 24 | + |
| 25 | +## 데이터 구조 확인 결과 |
| 26 | + |
| 27 | +### JS 레슨 필드 분류 |
| 28 | + |
| 29 | +| 필드 위치 | 레슨 유형 | 예시 | |
| 30 | +|-----------|-----------|------| |
| 31 | +| `step.eventLoopState` | event loop (js-1-x, js-4-3 등) | js-1-3 | |
| 32 | +| `step.scopeState` | 스코프 체인 (js-2-x 등) | js-2-1 | |
| 33 | +| `step.promiseState` | Promise (js-4-x 등) | js-4-1 | |
| 34 | +| `step.thisState` | this 바인딩 | js-3-x | |
| 35 | +| `step.prototypeState` | 프로토타입 체인 | js-3-x | |
| 36 | +| `step.memoryState` (nested stack/heap) | 참조값/객체 레슨 | js-5-x, js-7-x, js-9-x | |
| 37 | + |
| 38 | +→ **JS Memory 탭 필요**: `step.memoryState`가 있는 레슨만 (js-5-x, js-7-1, js-7-3, js-9-1, js-9-2) |
| 39 | +→ **Flow 탭만 필요**: 나머지 (eventLoopState/scopeState 등만 있는 레슨) |
| 40 | + |
| 41 | +### C/C++ 레슨 필드 |
| 42 | + |
| 43 | +- `step.stack`: top-level 배열 (메모리 스택 프레임) |
| 44 | +- `step.heap`: top-level 배열 (힙 변수) |
| 45 | +- `step.memoryChanges`: 변경 추적 (있을 수 있음) |
| 46 | + |
| 47 | +--- |
| 48 | + |
| 49 | +## 변경 범위 |
| 50 | + |
| 51 | +**파일 2개만 수정** |
| 52 | + |
| 53 | +1. `packages/frontend/src/features/courses/utils/visualizationData.ts` |
| 54 | +2. `packages/frontend/src/features/courses/components/LessonUnifiedView.tsx` |
| 55 | + |
| 56 | +--- |
| 57 | + |
| 58 | +## 구현 명세 |
| 59 | + |
| 60 | +### Step 1: visualizationData.ts에 함수 추가 |
| 61 | + |
| 62 | +```ts |
| 63 | +/** |
| 64 | + * C/C++ Memory 탭 노출 여부 판단 |
| 65 | + * - step.stack (top-level 배열, 비어있지 않음) |
| 66 | + * - step.heap (top-level 배열) |
| 67 | + * - step.memoryChanges |
| 68 | + */ |
| 69 | +export function hasClassicMemoryData(steps: LessonStep[]): boolean { |
| 70 | + return steps.some(step => { |
| 71 | + const s = step as Record<string, unknown>; |
| 72 | + return ( |
| 73 | + (Array.isArray(s.stack) && (s.stack as unknown[]).length > 0) || |
| 74 | + (Array.isArray(s.heap) && (s.heap as unknown[]).length > 0) || |
| 75 | + hasMeaningfulValue(s.memoryChanges) |
| 76 | + ); |
| 77 | + }); |
| 78 | +} |
| 79 | + |
| 80 | +/** |
| 81 | + * JS Memory 탭 노출 여부 판단 |
| 82 | + * - step.memoryState (nested {stack, heap}) 가 있으면 참조 그래프 탭 필요 |
| 83 | + * - eventLoopState/scopeState 등만 있으면 false |
| 84 | + */ |
| 85 | +export function hasJsMemoryData(steps: LessonStep[]): boolean { |
| 86 | + return steps.some(step => { |
| 87 | + const s = step as Record<string, unknown>; |
| 88 | + return hasMeaningfulValue(s.memoryState); |
| 89 | + }); |
| 90 | +} |
| 91 | +``` |
| 92 | + |
| 93 | +### Step 2: LessonUnifiedView.tsx 수정 |
| 94 | + |
| 95 | +```ts |
| 96 | +// 추가 import |
| 97 | +import { hasMeaningfulValue, hasClassicMemoryData, hasJsMemoryData } from '../utils/visualizationData'; |
| 98 | + |
| 99 | +// 기존 (78-80줄) |
| 100 | +const showMemoryTab = languageId === 'c' || languageId === 'cpp'; |
| 101 | +const showJsMemoryTab = languageId === 'javascript'; |
| 102 | + |
| 103 | +// 변경 후 — useMemo로 감싸서 불필요한 재계산 방지 |
| 104 | +// (nav.vizStepIndices는 useRoundNavigation 내부에서 이미 useMemo로 안정적) |
| 105 | +const showMemoryTab = useMemo( |
| 106 | + () => (languageId === 'c' || languageId === 'cpp') |
| 107 | + && hasClassicMemoryData(nav.vizStepIndices.map(i => steps[i])), |
| 108 | + [languageId, nav.vizStepIndices, steps] |
| 109 | +); |
| 110 | +const showJsMemoryTab = useMemo( |
| 111 | + () => languageId === 'javascript' |
| 112 | + && hasJsMemoryData(nav.vizStepIndices.map(i => steps[i])), |
| 113 | + [languageId, nav.vizStepIndices, steps] |
| 114 | +); |
| 115 | +``` |
| 116 | + |
| 117 | +기존 `useEffect` fallback (130-137줄)은 그대로 유지: |
| 118 | +```ts |
| 119 | +useEffect(() => { |
| 120 | + if (activeVizTab === 'memory' && !showMemoryTab) setActiveVizTab('flow'); |
| 121 | + if (activeVizTab === 'jsMemory' && !showJsMemoryTab) setActiveVizTab('flow'); |
| 122 | +}, [activeVizTab, showMemoryTab, showJsMemoryTab]); |
| 123 | +``` |
| 124 | + |
| 125 | +--- |
| 126 | + |
| 127 | +## 검증 케이스 |
| 128 | + |
| 129 | +| 레슨 | 데이터 | 예상 탭 | |
| 130 | +|------|--------|---------| |
| 131 | +| js-1-3 | eventLoopState만 | Flow 탭만 (탭 바 숨김) | |
| 132 | +| js-1-1~1-4, js-4-3 | eventLoopState만 | Flow 탭만 (탭 바 숨김) | |
| 133 | +| js-5-1~5-3 | memoryState(stack/heap) | Flow + JS Memory | |
| 134 | +| js-7-1, js-7-3, js-9-1, js-9-2 | memoryState(stack/heap) | Flow + JS Memory | |
| 135 | +| C 메모리 레슨 | step.stack/heap 있음 | Flow + Memory | |
| 136 | +| C 순수 flow 레슨 | step.stack 비어있거나 없음 | Flow 탭만 (탭 바 숨김) | |
| 137 | +| C++ 메모리 레슨 | step.stack/heap 있음 | Flow + Memory | |
| 138 | +| C 개념 전용 레슨 (전처리기 등) | 모든 viz 스텝 stack/heap 비어있음 | Flow 탭만 (**현재보다 개선**) | |
| 139 | +| js-7-2 | memoryState.heap만 있음 (stack 없음) | Flow + JS Memory | |
| 140 | + |
| 141 | +--- |
| 142 | + |
| 143 | +## 코드베이스 정밀검사 결과 (2026-03-05) |
| 144 | + |
| 145 | +검사 범위: `useLessonVisualization.ts`, `useRoundNavigation.ts`, 전체 lesson JSON |
| 146 | + |
| 147 | +| 항목 | 결과 | |
| 148 | +|------|------| |
| 149 | +| `memoryChanges` 필드 존재 여부 | `useLessonVisualization:407`에 분기 존재. 현재 JSON에는 없으나 미래 대비 유지 | |
| 150 | +| JS 레슨 top-level `stack` 사용 여부 | 없음. 전부 `step.memoryState.stack` (nested) | |
| 151 | +| C/C++ `memoryState` nested 사용 여부 | 없음. 전부 top-level `step.stack`/`step.heap` | |
| 152 | +| js-7-2 데이터 구조 | `memoryState.heap` 만 있음. `hasMeaningfulValue(memoryState)` → true ✓ | |
| 153 | +| C 개념 전용 레슨 (빈 stack/heap) | Memory 탭 숨김 — 현재보다 나은 동작 | |
| 154 | +| Delta format 영향 | 백엔드 seed 시 확장 완료 → 프론트엔드는 항상 완전한 스텝 수신 | |
| 155 | +| Carry-forward 영향 | `useLessonVisualization` 내부에서만 동작 → 판별 함수에 무관 | |
| 156 | +| `nav.vizStepIndices` 안정성 | `useRoundNavigation` 내 `useMemo([steps])` → 안정적 | |
| 157 | +| 성능 | `useMemo` 추가로 스텝 이동마다 불필요한 재계산 제거 | |
| 158 | + |
| 159 | +--- |
| 160 | + |
| 161 | +## 완료 기준 |
| 162 | + |
| 163 | +- [ ] `hasClassicMemoryData`, `hasJsMemoryData` 함수 추가 |
| 164 | +- [ ] LessonUnifiedView `showMemoryTab`, `showJsMemoryTab` 데이터 기반으로 교체 |
| 165 | +- [ ] js-1-3 접속 시 탭 바 없음 확인 |
| 166 | +- [ ] js-5-1 접속 시 Flow + JS Memory 탭 확인 |
| 167 | +- [ ] C 메모리 레슨 접속 시 Flow + Memory 탭 확인 |
| 168 | +- [ ] activeVizTab fallback 정상 동작 확인 (탭 사라질 때 flow로 복귀) |
0 commit comments