공개 는 헤더 「로고 이미지」에 이미지를 지정하면 화면이 엑박이 되는 제보였다.
image 위젯은 배경용으로 설계되어 {url,size,repeat,position} 객체를 내보내는데, 값 슬롯이
하나뿐인 apply 경로(propValue/cssVar/단일 styleProp)가 그 객체를 그대로 props 에 기록해
소비 컴포넌트가 [object Object] 를 URL 로 받았다. 예외도 콘솔 오류도 서버 로그도 남지
않는다 — 깨진 이미지 요청은 SPA catch-all 때문에 404 조차 아니라 200(HTML)이고, 편집기
미리보기는 정상이라 조작 중에는 이상이 보이지 않는다.
방어선을 넷으로 세웠다. 쓰기 축약(공용 헬퍼 scalarizeImageValue 단일 지점, 게이트는 위젯
이름이며 값 형태 sniffing 이 아니다) · 읽기 역조립(표현식 문자열도 되감아 업로드 1클릭에
소실되지 않게 한다) · 런타임 방어(업그레이드 전 화면을 위해 템플릿 Img 가 url 을 해석하고
손상값이면 src 를 아예 붙이지 않는다) · 저장 데이터 백필(업그레이드 스텝). 런타임과 백필은
완전히 같은 엄격 판정식(키 집합 ⊆ 4키 AND url 보유)을 쓴다 — 엔진의 느슨한 판정식을 백필에
이식하면 레이아웃 전수에서 정상 props 2,219건을 파괴한다(실측).
전수조사에서 파생한 인접 결함 넷을 함께 고쳤다.
- number 위젯이 코어 레지스트리에 미등록이라 「탭 표시 게시판 수」 같은 컨트롤이
「지원하지 않는 컨트롤」로 폴백해 편집 자체가 불가했다. nodeKey apply 는 coreProps 가
선언만 하고 엔진 switch 에 case 가 없어 무음 no-op 이었다.
- 상속(base)·주입(extension) 노드 중 바인딩을 가진 것이 data_bound 로 분류돼 편집이
열려 있었는데, 저장 마스킹이 그 노드를 통째로 폐기하므로 편집분이 오류도 경고도 없이
사라졌다(저장은 200 이고 history 는 clear 돼 undo 도 불가). 출처 잠금이 항상 우선하도록
판정 순서를 통일하고, 단일 판정 헬퍼로 인라인 편집·복제·Delete·잘라내기·드래그 commit
까지 전 표면을 같은 기준으로 막았다.
- prop 자리의 표현식 값을 위젯이 해석하지 못해 빈 컨트롤로 보이고, 조작하는 순간 환경설정
과의 연결이 소리 없이 끊겼다. 판정·배지·잠금·해제·복구를 ControlRenderer 공용 게이트
한 곳으로 올려 신규 위젯에도 자동 적용되게 했다. 「직접 지정으로 바꾸기」에는 「되돌리기」를
동반해 편도가 되지 않게 한다.
- 편집기 모드에서 updateTemplateData 가 빈 레이아웃으로 같은 reactRoot 에 두 번째 커밋을
걸어 편집기 트리를 통째로 제거했다. renderTemplate 의 편집기 분기가 비동기라 부팅 중
setGlobalState 가 그 커밋 뒤에 도착할 때만 발현하는 경합이었다.
78 lines
2.3 KiB
JSON
78 lines
2.3 KiB
JSON
{
|
|
"version": "1.0.0",
|
|
"layout_name": "e2e_sandbox",
|
|
"extends": "_user_base",
|
|
"meta": {
|
|
"title": "E2E Sandbox (user)",
|
|
"description": "레이아웃 편집기 E2E 전용 시드 화면. 제품 화면이 아니며 playwright:seed-layout 커맨드가 활성 디렉토리에만 설치한다.",
|
|
"seo": {
|
|
"enabled": false
|
|
}
|
|
},
|
|
"slots": {
|
|
"content": [
|
|
{
|
|
"type": "layout",
|
|
"name": "Container",
|
|
"props": {
|
|
"className": "py-6 px-4"
|
|
},
|
|
"children": [
|
|
{
|
|
"id": "e2e_sandbox_header",
|
|
"type": "composite",
|
|
"name": "Header",
|
|
"props": {
|
|
"siteName": "E2E 샌드박스"
|
|
}
|
|
},
|
|
{
|
|
"id": "e2e_sandbox_root",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "p-4 rounded border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800"
|
|
},
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "H2",
|
|
"props": {
|
|
"className": "text-lg font-bold text-gray-900 dark:text-gray-100"
|
|
},
|
|
"text": "E2E 샌드박스"
|
|
},
|
|
{
|
|
"type": "basic",
|
|
"name": "P",
|
|
"props": {
|
|
"className": "mt-1 text-sm text-gray-500 dark:text-gray-400"
|
|
},
|
|
"text": "레이아웃 편집기 E2E 가 컴포넌트를 넣고 저장하는 전용 화면입니다."
|
|
},
|
|
{
|
|
"id": "e2e_sandbox_slot",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "mt-4 min-h-24 p-3 rounded border border-dashed border-gray-300 dark:border-gray-600"
|
|
},
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Span",
|
|
"props": {
|
|
"className": "text-xs text-gray-400 dark:text-gray-500"
|
|
},
|
|
"text": "sandbox slot"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|