@nine-lab/nine-mu 0.1.334 → 0.1.336

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nine-lab/nine-mu",
3
- "version": "0.1.334",
3
+ "version": "0.1.336",
4
4
  "description": "AI-Driven Full-Stack Code Fabrication Engine",
5
5
  "type": "module",
6
6
  "main": "./dist/nine-mu.umd.js",
@@ -943,9 +943,19 @@
943
943
  }
944
944
 
945
945
  :host(nine-exception-hook) {
946
- display: block;
946
+ display: flex;
947
947
  width: 100%;
948
948
  height: 100%;
949
+ color: inherit;
950
+ font-size: inherit;
951
+ line-height: inherit;
952
+
953
+ /* 내부 모든 엘리먼트의 여백 계산법을 어드민 기본형에 동기화 */
954
+ .nine-error-container *,
955
+ .nine-error-container *::before,
956
+ .nine-error-container *::after {
957
+ box-sizing: border-box;
958
+ }
949
959
 
950
960
  .nine-error-container {
951
961
  padding: 30px;
@@ -1012,10 +1022,10 @@
1012
1022
  font-size: 14px;
1013
1023
  color: #718096;
1014
1024
  }
1025
+ }
1015
1026
 
1016
- @keyframes pulse {
1017
- 0% { opacity: 0.6; }
1018
- 50% { opacity: 1; }
1019
- 100% { opacity: 0.6; }
1020
- }
1027
+ @keyframes pulse {
1028
+ 0% { opacity: 0.6; }
1029
+ 50% { opacity: 1; }
1030
+ 100% { opacity: 0.6; }
1021
1031
  }
@@ -1,38 +1,36 @@
1
1
  // src/components/exception/ExceptionHook.js
2
2
  export class ExceptionHook extends HTMLElement {
3
+ // 💡 [핵심] 비공개 인스턴스 필드는 클래스 최상단에 먼저 선언해야 합니다.
4
+ #lastPath = window.location.pathname;
5
+ #hasError = false;
6
+ #cssPath = "/css/nine-exception-hook.css";
7
+
3
8
  constructor() {
4
9
  super();
5
- this.lastPath = window.location.pathname;
6
- this.hasError = false;
7
- this.cssPath = "/css/nine-exception-hook.css";
8
-
9
- // 💡 1. Shadow DOM(open 모드) 활성화 및 슬롯 공간 준비
10
+ // Shadow DOM(open 모드) 활성화
10
11
  this.attachShadow({ mode: 'open' });
11
-
12
- // 기본 구조 선언: 자식 엘리먼트들이 위치할 <slot>을 배치합니다.
13
-
14
12
  }
15
13
 
16
- connectedCallback() {
14
+ // src/components/exception/ExceptionHook.js 내부 수정
17
15
 
16
+ connectedCallback() {
18
17
  this.#renderer();
19
-
20
- // Shadow DOM 내부에 캡슐화된 스타일시트 링크 주입
21
18
  this.#injectShadowStylesheet();
22
19
 
23
- window.addEventListener('error', this.#handleRuntimeError.bind(this), true);
24
- window.addEventListener('unhandledrejection', this.#handlePromiseError.bind(this), true);
25
- window.addEventListener('popstate', this.#handleLocationChange.bind(this));
26
- document.addEventListener('click', this.#handleLocationChange.bind(this), true);
20
+ window.addEventListener('error', this.#handleRuntimeError, true);
21
+ window.addEventListener('unhandledrejection', this.#handlePromiseError, true);
22
+ window.addEventListener('popstate', this.#handleLocationChange);
23
+ document.addEventListener('click', this.#handleLocationChange, true);
27
24
  }
28
25
 
29
26
  disconnectedCallback() {
30
- window.removeEventListener('error', this.#handleRuntimeError);
31
- window.removeEventListener('unhandledrejection', this.#handlePromiseError);
27
+ window.removeEventListener('error', this.#handleRuntimeError, true);
28
+ window.removeEventListener('unhandledrejection', this.#handlePromiseError, true);
32
29
  window.removeEventListener('popstate', this.#handleLocationChange);
33
30
  document.removeEventListener('click', this.#handleLocationChange, true);
34
31
  }
35
32
 
33
+
36
34
  #renderer = () => {
37
35
  const customImport = nine.cssPath ? `@import "${nine.cssPath}/nine-mu.css";` : "";
38
36
 
@@ -45,52 +43,65 @@ export class ExceptionHook extends HTMLElement {
45
43
  <div class="nine-hook-core">
46
44
  <slot></slot>
47
45
  </div>
46
+ <div class="nine-exception-board"></div>
48
47
  `;
49
48
  }
50
49
 
51
- // 💡 2. Shadow Root 내부 전용 스타일 링크 주입 함수
52
50
  #injectShadowStylesheet() {
53
- if (this.shadowRoot.querySelector(`link[href="${this.cssPath}"]`)) return;
51
+ // 💡 내부 필드 호출 시에도 모두 this.#형식으로 매핑합니다.
52
+ if (this.shadowRoot.querySelector(`link[href="${this.#cssPath}"]`)) return;
54
53
 
55
54
  const link = document.createElement('link');
56
55
  link.rel = 'stylesheet';
57
- link.href = this.cssPath;
58
- // head가 아닌 shadowRoot에 집어넣어 글로벌 오염을 완벽히 격리합니다.
56
+ link.href = this.#cssPath;
59
57
  this.shadowRoot.appendChild(link);
60
58
  }
61
59
 
62
- #handleRuntimeError(event) {
63
- // Shadow DOM 외부의 자식 영역(Light DOM) 검증을 위해 여전히 this.contains 활용
64
- if (this.contains(event.target) || event.error) {
65
- this.#renderError(event.error?.message || event.message, event.error?.stack);
60
+ // src/components/exception/ExceptionHook.js
61
+
62
+ #handleRuntimeError = (event) => {
63
+ if (this.#hasError) return;
64
+
65
+ // 🎯 핵심 1: 리액트 에러는 내부 가상 DOM에서 발생하므로
66
+ // event.error가 존재하거나, 에러 타겟이 이 컴포넌트 하위에 속해있는지 확인합니다.
67
+ const isReactError = event.error || (event.target && this.contains(event.target));
68
+
69
+ if (isReactError) {
70
+ // 에러 스택과 메시지 추출
71
+ const message = event.error?.message || event.message || "알 수 없는 런타임 에러";
72
+ const stack = event.error?.stack || "Stack trace unavailable";
73
+
74
+ // 🎯 핵심 2: 리액트가 에러 때문에 화면을 통째로 날려버리기 전에
75
+ // 기본 렌더링 슬롯(<slot>)을 즉시 숨기고 Web Component 자체 에러 보드를 띄웁니다.
76
+ this.#renderError(message, stack);
77
+
78
+ // 브라우저 기본 에러 동작(콘솔 중복 출력 등)을 필요에 따라 제어하려면 추가
79
+ // event.preventDefault();
66
80
  }
67
81
  }
68
82
 
69
-
70
- #handlePromiseError(event) {
83
+ #handlePromiseError = (event) => {
84
+ if (this.#hasError) return;
71
85
  this.#renderError(event.reason?.message || String(event.reason), event.reason?.stack);
72
86
  }
73
87
 
74
- #handleLocationChange() {
88
+ #handleLocationChange = () => {
75
89
  setTimeout(() => {
76
- if (this.hasError && this.lastPath !== window.location.pathname) {
90
+ if (this.#hasError && this.#lastPath !== window.location.pathname) {
77
91
  console.log("✨ [nine-exception-hook] Shadow DOM 내부 예외 청소 및 정상 복구");
78
- this.lastPath = window.location.pathname;
79
- this.hasError = false;
92
+ this.#lastPath = window.location.pathname;
93
+ this.#hasError = false;
80
94
 
81
- // 💡 3. 복구 시 Shadow Root 내부의 에러 컨테이너만 타겟팅해서 제거
82
- const errorBox = this.shadowRoot.querySelector('.nine-error-container');
83
- if (errorBox) errorBox.remove();
95
+ const exceptionBoard = this.shadowRoot.querySelector('.nine-exception-board');
96
+ if (exceptionBoard) exceptionBoard.innerHTML = '';
84
97
 
85
- // 숨겨두었던 원래 본문 슬롯 구역을 다시 노출
86
98
  const coreContainer = this.shadowRoot.querySelector('.nine-hook-core');
87
99
  if (coreContainer) coreContainer.style.display = '';
88
100
  }
89
101
  }, 10);
90
102
  }
91
103
 
92
- async #sendToAiAction(errorMessage, errorStack) {
93
- // 💡 4. DOM 쿼리 탐색을 모두 this가 아닌 this.shadowRoot로 전환
104
+ #sendToAiAction = async (errorMessage, errorStack) => {
94
105
  const btn = this.shadowRoot.querySelector('.nine-ai-btn');
95
106
  const footer = this.shadowRoot.querySelector('.nine-error-footer');
96
107
  if (!btn) return;
@@ -121,14 +132,15 @@ export class ExceptionHook extends HTMLElement {
121
132
  }
122
133
 
123
134
  #renderError(errorMessage, errorStack) {
124
- if (this.hasError) return;
125
- this.hasError = true;
135
+ if (this.#hasError) return;
136
+ this.#hasError = true;
126
137
 
127
- // 💡 5. 리액트의 원본 마크업을 건드리지 않고, 슬롯이 감싸진 껍데기만 숨김 처리
128
138
  const coreContainer = this.shadowRoot.querySelector('.nine-hook-core');
129
139
  if (coreContainer) coreContainer.style.display = 'none';
130
140
 
131
- // Shadow DOM 내부에 렌더링할 마크업 구조 설계
141
+ const exceptionBoard = this.shadowRoot.querySelector('.nine-exception-board');
142
+ if (!exceptionBoard) return;
143
+
132
144
  const errorHTML = `
133
145
  <div class="nine-error-container">
134
146
  <h3 class="nine-error-title">⚠️ 시스템 예외 감지 (${this.tagName.toLowerCase()})</h3>
@@ -144,10 +156,8 @@ export class ExceptionHook extends HTMLElement {
144
156
  </div>
145
157
  `;
146
158
 
147
- // shadowRoot 최하단에 안전하게 안착시킵니다.
148
- this.shadowRoot.insertAdjacentHTML('beforeend', errorHTML);
159
+ exceptionBoard.innerHTML = errorHTML;
149
160
 
150
- // 버튼 이벤트 바인딩도 shadowRoot 기준 처리
151
161
  const aiBtn = this.shadowRoot.querySelector('.nine-ai-btn');
152
162
  if (aiBtn) {
153
163
  aiBtn.addEventListener('click', () => this.#sendToAiAction(errorMessage, errorStack));