redi 이슈 해결

This commit is contained in:
sheetEasy AI Team
2025-06-26 17:01:28 +09:00
parent 71036d3727
commit 2d8e4524b7
4 changed files with 421 additions and 37 deletions

View File

@@ -0,0 +1,103 @@
---
description:
globs:
alwaysApply: true
---
---
description:
globs:
alwaysApply: true
---
## Core Directive
You are a senior software engineer AI assistant. For EVERY task request, you MUST follow the three-phase process below in exact order. Each phase must be completed with expert-level precision and detail.
## Guiding Principles
- **Minimalistic Approach**: Implement high-quality, clean solutions while avoiding unnecessary complexity
- **Expert-Level Standards**: Every output must meet professional software engineering standards
- **Concrete Results**: Provide specific, actionable details at each step
---
## Phase 1: Codebase Exploration & Analysis
**REQUIRED ACTIONS:**
1. **Systematic File Discovery**
- List ALL potentially relevant files, directories, and modules
- Search for related keywords, functions, classes, and patterns
- Examine each identified file thoroughly
2. **Convention & Style Analysis**
- Document coding conventions (naming, formatting, architecture patterns)
- Identify existing code style guidelines
- Note framework/library usage patterns
- Catalog error handling approaches
**OUTPUT FORMAT:**
```
### Codebase Analysis Results
**Relevant Files Found:**
- [file_path]: [brief description of relevance]
**Code Conventions Identified:**
- Naming: [convention details]
- Architecture: [pattern details]
- Styling: [format details]
**Key Dependencies & Patterns:**
- [library/framework]: [usage pattern]
```
---
## Phase 2: Implementation Planning
**REQUIRED ACTIONS:**
Based on Phase 1 findings, create a detailed implementation roadmap.
**OUTPUT FORMAT:**
```markdown
## Implementation Plan
### Module: [Module Name]
**Summary:** [1-2 sentence description of what needs to be implemented]
**Tasks:**
- [ ] [Specific implementation task]
- [ ] [Specific implementation task]
**Acceptance Criteria:**
- [ ] [Measurable success criterion]
- [ ] [Measurable success criterion]
- [ ] [Performance/quality requirement]
### Module: [Next Module Name]
[Repeat structure above]
```
---
## Phase 3: Implementation Execution
**REQUIRED ACTIONS:**
1. Implement each module following the plan from Phase 2
2. Verify ALL acceptance criteria are met before proceeding
3. Ensure code adheres to conventions identified in Phase 1
**QUALITY GATES:**
- [ ] All acceptance criteria validated
- [ ] Code follows established conventions
- [ ] Minimalistic approach maintained
- [ ] Expert-level implementation standards met
---
## Success Validation
Before completing any task, confirm:
- ✅ All three phases completed sequentially
- ✅ Each phase output meets specified format requirements
- ✅ Implementation satisfies all acceptance criteria
- ✅ Code quality meets professional standards
## Response Structure
Always structure your response as:
1. **Phase 1 Results**: [Codebase analysis findings]
2. **Phase 2 Plan**: [Implementation roadmap]
3. **Phase 3 Implementation**: [Actual code with validation]

View File

@@ -0,0 +1,154 @@
---
description:
globs:
alwaysApply: false
---
## REDI Duplicate Identifier Prevention in Univer CE
### **Problem Analysis**
- **Root Cause**: REDI dependency injection system retains global service identifiers in memory even after Univer instance disposal
- **Trigger**: Component remounting causes duplicate service registration attempts
- **Symptoms**: "Identifier [service-name] already exists" console errors
### **Core Prevention Patterns**
#### **1. Global Instance Management**
```typescript
// ✅ DO: Use singleton pattern with proper state tracking
const UniverseManager = {
async createInstance(container: HTMLElement): Promise<any> {
const state = getGlobalState();
// Check existing instance with complete validation
if (state.instance && state.univerAPI &&
!state.isInitializing && !state.isDisposing) {
return { univer: state.instance, univerAPI: state.univerAPI };
}
// Wait for ongoing operations
if (state.isInitializing && state.initializationPromise) {
return state.initializationPromise;
}
}
};
// ❌ DON'T: Create new instances without checking global state
const univer = createUniver({ /* config */ }); // Direct creation
```
#### **2. Component Mount Lifecycle**
```typescript
// ✅ DO: Smart initialization with state checks
useEffect(() => {
const existingUniver = UniverseManager.getInstance();
const state = getGlobalState();
// Reuse existing instance if available and stable
if (existingUniver && state.univerAPI &&
!UniverseManager.isInitializing() && !UniverseManager.isDisposing()) {
setIsInitialized(true);
return;
}
// Wait for ongoing operations
if (UniverseManager.isInitializing() || UniverseManager.isDisposing()) {
const waitTimer = setInterval(() => {
if (!UniverseManager.isInitializing() && !UniverseManager.isDisposing()) {
clearInterval(waitTimer);
// Check and initialize as needed
}
}, 100);
return () => clearInterval(waitTimer);
}
}, []); // Empty dependency array to prevent re-execution
// ❌ DON'T: Include dependencies that cause re-initialization
useEffect(() => {
initializeUniver();
}, [initializeUniver]); // This causes re-execution on every render
```
#### **3. Complete Cleanup Strategy**
```typescript
// ✅ DO: Implement thorough REDI state cleanup
forceReset(): void {
// Standard disposal
if (state.instance) {
state.instance.dispose();
}
// REDI global state cleanup attempt
if (typeof window !== "undefined") {
const globalKeys = Object.keys(window).filter(key =>
key.includes('redi') || key.includes('REDI') ||
key.includes('univerjs') || key.includes('univer')
);
globalKeys.forEach(key => {
try {
delete (window as any)[key];
} catch (e) {
console.warn(`Global key ${key} cleanup failed:`, e);
}
});
}
// Reset all state flags
Object.assign(state, {
instance: null,
univerAPI: null,
isInitializing: false,
isDisposing: false,
initializationPromise: null,
lastContainerId: null,
});
}
```
### **4. State Validation Patterns**
```typescript
// ✅ DO: Multi-level state validation
const isInstanceReady = (state: GlobalUniverState): boolean => {
return !!(
state.instance &&
state.univerAPI &&
!state.isInitializing &&
!state.isDisposing &&
state.lastContainerId
);
};
// ❌ DON'T: Simple existence check
if (state.instance) { /* insufficient validation */ }
```
### **5. Debug Tools Integration**
```typescript
// ✅ DO: Provide comprehensive debugging tools
window.__UNIVER_DEBUG__ = {
getGlobalUniver: () => UniverseManager.getInstance(),
getGlobalState: () => getGlobalState(),
forceReset: () => UniverseManager.forceReset(),
completeCleanup: () => UniverseManager.completeCleanup(),
};
```
### **Error Resolution Steps**
1. **Immediate**: Call `window.__UNIVER_DEBUG__.completeCleanup()` in console
2. **Prevention**: Use empty dependency arrays in useEffect for initialization
3. **Validation**: Always check both `instance` and `univerAPI` existence
4. **Cleanup**: Implement thorough REDI global state cleanup in disposal methods
### **Common Anti-Patterns to Avoid**
- ❌ Re-initializing on every component render
- ❌ Not waiting for ongoing initialization/disposal operations
- ❌ Incomplete state validation before reuse
- ❌ Missing REDI global state cleanup
- ❌ Using complex dependency arrays in initialization useEffect
### **Best Practices**
- ✅ Implement singleton pattern with proper state management
- ✅ Use polling-based waiting for state transitions
- ✅ Provide debug tools for runtime state inspection
- ✅ Separate initialization concerns from component lifecycle
- ✅ Implement both standard and emergency cleanup methods

View File

@@ -1,7 +1,12 @@
import { useState } from "react";
import { Button } from "./components/ui/button";
import HomeButton from "./components/ui/homeButton";
import EditSheetViewer from "./components/sheet/EditSheetViewer";
import { lazy, Suspense } from "react";
// 동적 import로 EditSheetViewer 로드 (필요할 때만)
const EditSheetViewer = lazy(
() => import("./components/sheet/EditSheetViewer"),
);
function App() {
const [showTestViewer, setShowTestViewer] = useState(false);
@@ -52,9 +57,20 @@ function App() {
{/* 메인 콘텐츠 */}
<main className="h-[calc(100vh-4rem)]">
{showTestViewer ? (
// 테스트 뷰어 표시
// 동적 로딩된 테스트 뷰어 표시
<div className="h-full">
<EditSheetViewer />
<Suspense
fallback={
<div className="h-full flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">🚀 ...</p>
</div>
</div>
}
>
<EditSheetViewer />
</Suspense>
</div>
) : (
// 메인 페이지

View File

@@ -44,6 +44,7 @@ declare global {
getGlobalState: () => GlobalUniverState;
clearGlobalState: () => void;
forceReset: () => void;
completeCleanup: () => void;
};
}
}
@@ -213,6 +214,33 @@ const UniverseManager = {
}
}
// REDI 전역 상태 완전 정리 시도
try {
// 브라우저의 전역 REDI 상태 정리 (가능한 경우)
if (typeof window !== "undefined") {
// REDI 관련 전역 객체들 정리
const globalKeys = Object.keys(window).filter(
(key) =>
key.includes("redi") ||
key.includes("REDI") ||
key.includes("univerjs") ||
key.includes("univer"),
);
globalKeys.forEach((key) => {
try {
delete (window as any)[key];
} catch (e) {
console.warn(`전역 키 ${key} 정리 실패:`, e);
}
});
console.log("🧹 REDI 전역 상태 정리 시도 완료");
}
} catch (error) {
console.warn("⚠️ REDI 전역 상태 정리 중 오류:", error);
}
state.instance = null;
state.univerAPI = null;
state.isInitializing = false;
@@ -221,37 +249,72 @@ const UniverseManager = {
state.lastContainerId = null;
window[GLOBAL_UNIVER_KEY] = null;
console.log("🔄 전역 Univer 상태 강제 리셋 완료");
console.log("🔄 전역 Univer 상태 강제 리셋 완료 (REDI 정리 포함)");
},
// 완전한 정리 메서드 (REDI 포함)
async completeCleanup(): Promise<void> {
const state = getGlobalState();
if (state.isDisposing) {
console.log("🔄 이미 정리 진행 중...");
return;
}
state.isDisposing = true;
try {
console.log("🗑️ 완전한 정리 시작 (REDI 포함)");
if (state.instance) {
await state.instance.dispose();
}
// 약간의 대기 후 REDI 상태 정리
await new Promise((resolve) => setTimeout(resolve, 100));
// REDI 전역 상태 정리 시도
this.forceReset();
console.log("✅ 완전한 정리 완료");
} catch (error) {
console.error("❌ 완전한 정리 실패:", error);
} finally {
state.isDisposing = false;
}
},
};
// 전역 디버그 객체 설정
if (typeof window !== "undefined") {
// 전역 상태 초기화
initializeGlobalState();
// 전역 디버그 객체 설정을 위한 헬퍼 함수 (모듈 레벨 실행 방지)
const setupDebugTools = (): void => {
if (typeof window !== "undefined" && !window.__UNIVER_DEBUG__) {
// 전역 상태 초기화 (필요한 경우에만)
initializeGlobalState();
// 디버그 객체 설정
window.__UNIVER_DEBUG__ = {
getGlobalUniver: () => UniverseManager.getInstance(),
getGlobalState: () => getGlobalState(),
clearGlobalState: () => {
const state = getGlobalState();
Object.assign(state, {
instance: null,
univerAPI: null,
isInitializing: false,
isDisposing: false,
initializationPromise: null,
lastContainerId: null,
});
window[GLOBAL_UNIVER_KEY] = null;
console.log("🧹 전역 상태 정리 완료");
},
forceReset: () => UniverseManager.forceReset(),
};
// 디버그 객체 설정
window.__UNIVER_DEBUG__ = {
getGlobalUniver: () => UniverseManager.getInstance(),
getGlobalState: () => getGlobalState(),
clearGlobalState: () => {
const state = getGlobalState();
Object.assign(state, {
instance: null,
univerAPI: null,
isInitializing: false,
isDisposing: false,
initializationPromise: null,
lastContainerId: null,
});
window[GLOBAL_UNIVER_KEY] = null;
console.log("🧹 전역 상태 정리 완료");
},
forceReset: () => UniverseManager.forceReset(),
completeCleanup: () => UniverseManager.completeCleanup(),
};
console.log("🐛 디버그 객체 설정 완료: window.__UNIVER_DEBUG__");
}
console.log("🐛 디버그 객체 설정 완료: window.__UNIVER_DEBUG__");
}
};
/**
* Univer CE + 파일 업로드 오버레이
@@ -520,28 +583,76 @@ const TestSheetViewer: React.FC = () => {
mountedRef.current = true;
console.log("🎯 컴포넌트 마운트됨");
// 기존 전역 인스턴스 확인 및 재사용
// 디버그 도구 설정 (컴포넌트 마운트 시에만)
setupDebugTools();
// 강화된 기존 인스턴스 확인 및 재사용
const existingUniver = UniverseManager.getInstance();
if (existingUniver && !UniverseManager.isInitializing()) {
console.log("♻️ 기존 전역 Univer 인스턴스 재사용");
const state = getGlobalState();
// 기존 인스턴스가 있고 정상 상태면 재사용
if (
existingUniver &&
state.univerAPI &&
!UniverseManager.isInitializing() &&
!UniverseManager.isDisposing()
) {
console.log("♻️ 기존 전역 Univer 인스턴스와 univerAPI 재사용");
setIsInitialized(true);
// 기존 인스턴스의 컨테이너가 현재 컨테이너와 다른 경우 갱신
if (
containerRef.current &&
state.lastContainerId !== containerRef.current.id
) {
console.log("🔄 컨테이너 정보 갱신");
state.lastContainerId = containerRef.current.id;
}
return;
}
// 컨테이너 준비 후 초기화
// 초기화 중이거나 정리 중인 경우 대기
if (UniverseManager.isInitializing() || UniverseManager.isDisposing()) {
console.log("⏳ 초기화/정리 중이므로 대기");
const waitTimer = setInterval(() => {
if (
!UniverseManager.isInitializing() &&
!UniverseManager.isDisposing()
) {
clearInterval(waitTimer);
const currentUniver = UniverseManager.getInstance();
if (currentUniver && getGlobalState().univerAPI) {
console.log("✅ 대기 후 기존 인스턴스 재사용");
setIsInitialized(true);
} else if (containerRef.current && mountedRef.current) {
console.log("🚀 대기 후 새 인스턴스 초기화");
initializeUniver();
}
}
}, 100);
return () => clearInterval(waitTimer);
}
// 완전히 새로운 초기화가 필요한 경우만 진행
const initTimer = setTimeout(() => {
if (containerRef.current && !UniverseManager.isInitializing()) {
console.log("🚀 컴포넌트 마운트 시 Univer 초기화");
// 마운트 상태와 컨테이너 재확인
if (
containerRef.current &&
mountedRef.current &&
!UniverseManager.isInitializing()
) {
console.log("🚀 컴포넌트 마운트 시 새 Univer 초기화");
initializeUniver();
}
}, 100); // 짧은 지연으로 DOM 완전 준비 보장
}, 100); // DOM 완전 준비 보장
return () => {
clearTimeout(initTimer);
mountedRef.current = false;
console.log("👋 컴포넌트 언마운트됨");
};
}, [initializeUniver]);
}, []); // 의존성 배열을 빈 배열로 변경하여 한 번만 실행
// 컴포넌트 언마운트 시 정리 (전역 인스턴스는 유지)
useEffect(() => {