공개 제보 https://github.com/gnuboard/g7/issues/122 대응 — 초기 부트스트랩 리소스(다국어 병합·컴포넌트 정의·라우트·확장 번들·템플릿 dist)를 캐시 버전 디렉토리(public/build/ext/{v}/)에 실파일로 게시해 웹서버가 rewrite 전에 직접 서빙한다. 부트 임계 경로의 PHP 왕복을 제거하고(실측 TTFB 131~144ms → 1~7ms), 미게시·부분게시·GC 직후에는 fetch·태그·번들 3계층이 종전 API 로 즉시 폴백한다. - 게시: 원자적 tmp→rename→manifest(존재=완료), 캐시 락 단일 실행, 인라인 GC (현재+직전 1개), incrementExtensionCacheVersion 단일 지점 terminating 트리거 + blade 자가 치유 + 설치기 태스크(best_effort) + 일일 cleanup 스케줄, sudo 업데이트 대비 소유권 정상화(normalizeOwnership)·prune/백업 제외 - 프론트(engine-v1.61.0): blade 주입 cache_version 1급 시드(이중 부트 로드 제거), fetchStaticFirst 즉시 폴백, ComponentRegistry 버전 키드 매니페스트, ModuleAssetLoader 번들 정적→레거시 폴백, asset-url-recovery staticToLegacy 역변환 - 폴백 API 품질: lang/components/routes ETag+304 + 환경 분기 Cache-Control, 열화 라우트 스냅샷 공개 캐시 금지(서버측 캐시 회피와 대칭), 게시 .htaccess mod_deflate + nginx gzip 스니펫(압축 전송량 회귀 방지) - SEO 정합: 봇 HTML 은 GC 대상 정적 URL 미사용(allowStatic:false), props $switch 봇측 해석 구현(engine-v1.56.0 패리티), 패리티 룰 expression-dialect 그룹 신설, 상주 allow 헤더 제거로 잠금 복원, _comment* 접두 주석 키 분류 - 검증: 전 수정 red→green 4단계, Playwright 라이브 21건, Chrome MCP 24축+M1~M3, 봇 curl 3축, 캐시 저장소(file/redis/database) 축 판정, 히스토리·공개이슈·커밋 이력 전수 재조사 반영 - 코어 7.0.10, engine-v1.61.0. kill-switch: G7_STATIC_CACHE=false
206 KiB
Template Engine Changelog
이 문서는 그누보드7 템플릿 엔진의 내부 개발 버전 이력입니다.
engine-v1.x.x버전은 그누보드7 릴리스 버전과 독립적입니다.형식: Keep a Changelog
[engine-v1.61.0] - 2026-08-25
Added
부트스트랩 리소스 정적 게시(bake) 우선 로더 + API 폴백 (#122)
- 서버가 병합 결과물(routes/lang/components)을 캐시 버전 디렉토리(
/build/ext/{v}/…)에 실파일로 게시하면, blade 가window.G7Config.staticBase를 주입하고 프론트 로더가 그 정적 경로를 우선 시도한다. 정적 응답이!ok(부분 게시·GC 직후 404 포함)이거나 네트워크 실패면 즉시 종전 API URL 로 폴백한다 (fetchStaticFirst— legacy 측은 기존fetchWithRetry네트워크 복원력 유지, 폴백 발생은 console.warn 1줄로 관측 가능). assetUrl.ts에extStaticBase()/extStaticVersion()/extStaticUrl()/staticToLegacy()추가 — 서버측AssetUrl의 게시 트리 규약과 경로 규칙 1:1.staticBase미주입(비프로덕션/kill-switch/미게시)이면 전 리소스가 종전 API 직행으로, 기존 동작과 바이트 동일하다.- 소비자 전환: TemplateApp routes(초기 burst + 핸드셰이크 재로드 — 재로드는 새 버전 정적 경로 조합, 미게시 시 legacy 폴백) · TranslationEngine lang(
bustCache재로드는 목적상 legacy 직행) · ComponentRegistry components(편집기 v0 경로는 legacy 유지). - 태그 계층 자가 복구:
asset-url-recovery파샬에staticToLegacy역변환 추가 — 실패한/build/ext/{v}/…태그 자산(<link>/<script>)을 종전/api/…URL 로 1회 전환한다 (GC 된 구버전 자산을 참조하는 캐시된 HTML 방어,/build/core/**는 계속 변환 제외, 단방향 1회·자동 reload 금지 불변식 유지). ModuleAssetLoader확장 병합 번들(JS/CSS)도 동일 폴백 — 정적 번들 URL 은 1회만 시도하고 미스 시staticToLegacy+convertToCurrentMode로 종전 API URL 에 합류한다 (JS 는 기존 재시도 예산을 레거시에서 이어가고, CSS 는 같은<link>의 href 를 1회 교체). 종전에는 같은 정적 URL 만 재시도해 게시본 소실 시 확장 핸들러가 조용히 전부 미등록되었다.- 그 역변환 결과는 언제나 확장자 형태이므로, 확장자 없는 형태로 이미 확정된 모드에서는 결과를 다시
toExtensionless로 넘긴다. 확장자 주소를 가로채는 서버(자산 URL 이중 모드의 대상)에서 CSS<link>는 교체 예산이 1회뿐이라, 그 한 번이 확장자 형태로 끝나면 스타일이 영구히 붙지 않았다 (<script>는 재시도 예산이 남아 다음 시도에서 모드 전환 분기가 발동하므로 영향 없음).
Fixed
stale 캐시버전 재방문의 부트 이중 로드 (#122)
- localStorage
g7_cache_version이 stale 이면 첫 burst 가 구버전?v로 나가고, config 핸드셰이크가 routes 재로드 + lang(ko·en)_=버스터 재다운로드를 유발했다 (~500KB 중복, 부트1.3s 연장). blade 는 이미 현재 버전을부트 사이 bump 는 종전대로 핸드셰이크가 복구한다.G7Config.cache_version으로 주입하고 있었으므로, TemplateApp 의 캐시 버전 시드를 blade 주입값 우선(부재 시 localStorage 폴백)으로 교체하고, 핸드셰이크 재로드 판정 기준을 "이번 burst 가 실제 사용한 버전" 으로 바꿨다. 렌더 ComponentRegistry.loadComponents()에 옵셔널cacheVersion파라미터를 추가해 components.json 요청에?v를 부착하고, 정적 manifestCache 키에 버전을 포함해 확장 라이프사이클 전후의 stale 매니페스트 교차 오염을 막았다 (편집기 경로는 버전 미전달 — v0 캐시 키 + 무버전 URL 하위 호환).
[engine-v1.60.6] - 2026-08-22
Fixed
정규식 lookbehind 로 인한 구형 Safari 전면 부팅 실패
DataBindingEngine의 정규식 2건(preprocessTranslationTokens·extractVariablesFromExpression)이 ES2018 lookbehind((?<!...))를 쓰고 있었다. 정규식 리터럴은 스크립트 파싱 단계에서 검증되므로, lookbehind 를 모르는 엔진(WebKit 은 Safari 16.4 에서 구현)은 코어 엔진 번들을 한 줄도 실행하지 못한다. 번들은async/defer없는 동기 classic 스크립트라 폴백 경로도 없어, iOS 15 대 기기에서 사이트가 통째로 뜨지 않았다.- 두 정규식을 lookbehind 없는 형태로 교체했다 — 선행 따옴표/구분자를 소비한 뒤 캡처 그룹으로 분기한다. 식별자 패턴은 구분자가
match[1], 식별자명이match[2]로 이동했다. - 동작은 완전히 동일하다: 무작위 200,034 표본(손으로 고른 경계 케이스 34건 포함) 퍼징에서 교체 전/후 결과 불일치 0건.
- 이 2건이 유일한 병목이었다 — 제거 후 코어 엔진 번들의 파싱 하한이 Safari 16.4 → 14.1 로 내려간다(esbuild 타깃 판정). 빌드 타깃 하향은 해법이 아니다: esbuild 는 lookbehind 를
new RegExp(...)로 옮길 뿐이라 파싱 오류가 런타임 오류로 이동한다. - 재발 방지: 배포 JS 산출물의 브라우저 하한 초과 문법을 정적 검사가 차단한다. 특히 부팅 임계 번들(코어 엔진 · 템플릿 컴포넌트)은 하한과 같은 버전이라도 정규식 리터럴 전용 문법을 금지한다 — 이 두 파일이 파싱되지 않으면 안내 화면조차 렌더되지 않기 때문이다.
[engine-v1.60.5] - 2026-08-19
Fixed
화살표/함수 파라미터 배열 구조분해 미지원 회귀 (장바구니/바로구매 불능·권한 computed 공백)
- engine-v1.60.0 의 SafeExpressionEvaluator 교체가 구 평가기(
new Function)가 허용하던 화살표·함수 파라미터의 배열 구조분해(([k, v]) =>,([, vid]) =>)를 수용하지 않아, 해당 문법을 쓰는 표현식이 전부Expected ")" but found "=>"로 파싱 실패했다. 액션 params 는 미평가 원문 문자열 그대로 서버에 전송되어 422 가 되고(스토어프론트 장바구니 담기·바로 구매가 전 상품에서 불능), 게시판 환경설정의 권한 기본값 computed 4종이 계산되지 않아 권한 섹션이 비었으며, 플러그인 설정·이커머스 폼의 검증 오류 표시(([field, messages]) => …)도 발화 시점에 같은 실패에 걸리는 상태였다. 저장소 레이아웃 54개 파일 104곳이 이 문법을 사용 중이었다. tryParseParamList에 배열 패턴(식별자 + 홀/elision, 파라미터 기본값 조합 포함)을 추가하고bindParams가 JS iterator 시맨틱(비-iterable 은 TypeError)으로 분해 바인딩하도록 했다. 중첩 패턴·rest·객체 패턴은 배포 레이아웃 사용 0건이라 지원하지 않는다(발견 시 arrow 아님으로 안전 되돌림).- 회귀 잠금: 사용 형태 전수(단일/쌍/홀/기본값/function 선언 파라미터/실전
_purchase_card.json본문·게시판 권한 computed·검증 오류 표시) 단위 테스트 + 배포 번들 E2E(destructuring_param) 추가.
[engine-v1.60.4] - 2026-08-14
Security
legacy 접근자를 통한 프로토타입 도달 차단
Objectfacade 에서getPrototypeOf/setPrototypeOf/defineProperty를 제거했지만, 같은 능력을 모든 객체가 상속으로 제공하는__lookupGetter__/__lookupSetter__/__defineGetter__/__defineSetter__로 되찾을 수 있었다. 이 4종은 프로퍼티를 키가 아니라 문자열 인자로 지목하므로 키 정규화(normalizeKey)를 원리상 거치지 않는다. 네 이름을 금지 프로퍼티에 추가해 dot·computed·문자열 조립 형태를 한꺼번에 막았다.- 임의 코드 실행(RCE)으로는 이어지지 않았다 —
Function/eval도달은 여전히constructor키를 요구하고 그 경로는 이미 차단되어 있었다. 막은 것은 페이지 전역 프로토타입 오염과 빌트인 메서드 변조/삭제로 인한 전역 장애다. Object.assign이 source 의__proto__키로 대상의 프로토타입을 바꾸던 경로도 닫았다. 네이티브assign은 대입([[Set]]) 이라JSON.parse('{"__proto__":…}')결과를 합칠 때 setter 가 깨어났다 — 이제 항상 own 데이터 프로퍼티로 정의하며, 금지 키는 복사하지 않고 거부한다.- 저장측 검증과 정적 검사에도 같은 패턴을 넣어 세 계층을 맞췄다. 배포 레이아웃 전수에서 이 4개 이름 사용은 0건이라 정상 표현식이 막히는 회귀는 없다(
toLocaleString·Object.assign·Object.create정당 사용은 그대로 동작).
[engine-v1.60.3] - 2026-08-14
Security
선행 슬래시 런도 authority 로 접어 판정 (engine-v1.60.2 후속)
- v1.60.2 의 정규화는 백슬래시를 슬래시로 바꾸기만 해서,
/\/host/x.js가 정규화 후 슬래시 3개(///host/x.js)가 됐다. 브라우저는 선행 슬래시가 몇 개든 authority 시작으로 접으므로(///host≡//host,https:///host≡https://host) 이 형태도 외부 호스트에서 로드된다. 정규화에 선행 슬래시 런 접기를 추가해 런타임·저장측·정적 검사 세 계층이 같은 호스트를 보도록 맞췄다. - 경로 중간의 연속 슬래시(
/js//a.js)는 브라우저도 경로로 두므로 건드리지 않는다(과차단 없음). - 이 형태의 실질 영향은 저장측이었다 — 런타임은
new URL로 호스트를 뽑아 이미 올바르게 판정하고 있었고, 저장측 신뢰 호스트 추출만 갈려 있었다(코어TrustedScriptHosts수정분 참조).
[engine-v1.60.2] - 2026-08-14
Security
scripts[].src same-origin 판정의 authority 우회 차단 (KVE-2026-1915 B-2 후속)
- 원격 스크립트 차단이
//접두·scheme 존재·/시작이라는 문자열 접두 검사로만 same-origin 을 판정했다. 그런데 브라우저 URL 파서는 (a) 파싱 전에 ASCII tab·개행을 제거하고 (b) http/https 에서 백슬래시를 슬래시와 동등하게 처리하므로,/\/evil.com/x.js·/\evil.com/x.js·/{tab}/evil.com/x.js같은 형태가 검사를 통과한 뒤 실제로는https://evil.com/x.js로 해석되어 선언되지 않은 외부 스크립트가 그대로 로드됐다(실측:new URL로 6형태 전부 외부 origin 해석). - 판정 전에 브라우저와 동일하게 정규화(tab·LF·CR 제거 → 백슬래시를 슬래시로)한 뒤 접두 검사를 적용하도록 고쳤다. 경로 중간의 백슬래시·탭(
/js/a\b.js)은 authority 를 만들지 않으므로 종전대로 same-origin 으로 통과한다(과차단 없음). - 저장측(
SafeLayoutExpressions·NoExternalUrls)과 정적 검사(layout-scripts-src-same-origin)도 동일 정규화를 공유한다 — 세 계층이 같은 판정 로직을 쓰고 있었으므로 한 형태로 셋이 함께 뚫려 있었다.
화이트리스트 전역의 Object.assign/freeze 변조 차단
delete는 화이트리스트 전역(Math/JSON/Date등)을 identity 로 차단하는데, facade 에 남긴assign/freeze는 대상을 검사하지 않아Object.assign(Math, { floor: … })로 공유 전역을 영구 변조할 수 있었다. 화이트리스트 전역은 실제 전역 참조를 노출하므로 그 변조는 페이지 전체(엔진·모듈·플러그인)에 지속된다. 두 메서드에도delete와 동일한 대상 검사를 적용했다. 일반 객체 대상assign/freeze는 그대로 동작한다.
[engine-v1.60.1] - 2026-08-14
Security
화이트리스트 평가기의 비-문자열 computed 키 · Object 리플렉션 static 을 통한 샌드박스 탈출 차단 (KVE-2026-1915)
SafeExpressionEvaluator의 프로퍼티 접근 하드닝이 문자열 키만 검사해, 배열/객체 키가''[['constructor']][['constructor']]('code')()처럼 JS ToPropertyKey 강제변환으로constructor에 도달하던 탈출을 막지 못했다. 키를 접근 전에 1회 정규화(심볼 외String()강제변환)한 뒤 금지 프로퍼티(constructor/__proto__/prototype)를 차단하고 그 정규화된 원시 키로만 접근하도록 고쳤다 — 배열·중첩 배열·객체toString강제변환·문자열 조립 등 모든 우회 형태가 접근 시점에 거부되며 재변환(TOCTOU) 여지도 없다.evalMember/evalCall/delete세 경로 모두 적용.- 화이트리스트 전역
Object를 네이티브 그대로 노출해Object.getOwnPropertyDescriptor(Object.getPrototypeOf(String), 'constructor').value로 Function 에 도달하거나Object.setPrototypeOf/defineProperty로 프로토타입을 오염시킬 수 있었다. 이 static 들은 프로퍼티 키가 아니라 문자열 인자로 프로퍼티를 지목하므로 키 정규화로는 잡히지 않는다. 리플렉션·프로토타입·디스크립터 계열 static 을 제거하고 순수 데이터 계열(keys/values/entries/assign/fromEntries/create/freeze/isFrozen)만 노출하는 facade 로 교체했다.create는 프로토타입/디스크립터를 읽지도 쓰지도 않아(신규 객체 생성만) 탈출 벡터가 아니며, 레이아웃이Object.assign(Object.create(null), …)로 정당하게 사용한다. - 저장측(
app/Rules/SafeLayoutExpressions.php)·정적 검사(layout-expression-dangerous-token)도 동형으로 넓혔다: 금지 프로퍼티 이름의 따옴표 문자열을 위치 무관 차단(computed 키['constructor']·중첩 배열 키[['constructor']]·리플렉션 문자열 인자…, 'constructor')포함), Object 리플렉션 static 이름 차단. 문자열 조립 난독화(['const' + 'ructor'])는 정적 토큰 매칭이 불가능하므로 런타임 인터프리터가 최종 게이트다.
[engine-v1.60.0] - 2026-08-14
Fixed
표현식 평가기가 statement 본문(IIFE)을 거부해 저장이 원문 문자열로 전송되던 회귀 (KVE-2026-1915 후속)
- engine-v1.59.0 에서
new Function→SafeExpressionEvaluator(AST 인터프리터)로 교체하며, 기존 30개 레이아웃이 쓰던(function() { const …; if (…) return {…}; })()/(() => { … })()형태의 statement 본문 IIFE 를 파싱 단계에서 거부하게 됐다.DataBindingEngine.resolveBindings는 평가 실패 시 원본 문자열을 그대로 돌려주므로, 이 식을 body 로 쓰던 저장 액션은 미해석{{…}}문자열을 서버로 전송했고 서버는 아무것도 저장하지 못한 채 성공(200)을 반환했다. 발현: 이커머스 설정에서 문의 게시판을 지정해도 저장되지 않고, 게시판/관리자 설정 저장, 카테고리 부모 선택 옵션, 배송비 단위 표기 등 동일 형태를 쓰던 화면이 조용히 동작하지 않았다(예외·경고 없음). SafeExpressionEvaluator에 함수/화살표 블록 본문 해석을 추가했다: 함수 표현식(function (…) { … }, 명명 함수 재귀 포함)·화살표 블록 본문·const/let선언·if/else·for…of(+break/continue)·return·try/catch/finally·delete·기본 파라미터. 모두 인터프리터가 트리워킹으로 실행하며 코드 문자열 컴파일(eval/new Function)은 여전히 쓰지 않는다.- 보안 경계는 그대로다 — KVE-2026-1915 의 탈출 벡터는 "function 키워드" 자체가 아니라
''.constructor.constructor('code')()같은 프로퍼티 체인을 통한 Function 생성자 접근이었고, 그 차단(constructor/__proto__/prototype접근,Function/eval/Reflect등 위험 전역, 비화이트리스트new)은 statement 본문 안에서도 동일하게 적용된다. 함수 표현식은 네이티브 컴파일이 아니라 해석기 클로저로만 실행되므로 새 탈출 경로가 생기지 않는다. 추가로delete는 화이트리스트 전역 객체(Math/JSON/Array등)의 프로퍼티를 지우지 못하도록 identity 로 차단한다. 대입(=)·증감(++/--)·복합대입·sequence(,)·비트/시프트/거듭제곱 연산자는 계속 거부한다.
[engine-v1.59.0] - 2026-08-13
Security
레이아웃 표현식 평가를 화이트리스트 AST 인터프리터로 교체 (KVE-2026-1915)
- 신규
SafeExpressionEvaluator.ts— 표현식 문자열을 토크나이저 → Pratt 파서 → AST 트리워킹 인터프리터로 해석한다.eval/new Function/with(ctx)를 일절 쓰지 않으므로''.constructor.constructor('code')()형태의 샌드박스 탈출(CWE-184/CWE-94)이 원천 차단된다. - 위험 지점 3곳을 이 평가기로 통일했다:
DataBindingEngine.evaluateExpression(주 평가 경로,TranslationEngine의$t:파라미터 평가 포함 200여 소비처),TemplateApp.evaluateComputedExpression(computed),TemplateApp.evaluateScriptCondition(scripts[].if). 종전evaluateComputedExpression/evaluateScriptCondition은with(ctx)로 필터조차 거치지 않아 더 위험했다. - 차단:
constructor/__proto__/prototype프로퍼티 접근(dot·문자열 리터럴 computed·런타임 해석된 computed 키 모두),Function/eval/globalThis/window/Reflect/Proxy등 위험 전역, 함수 생성(function키워드), 할당·증감, 비트/시프트/거듭제곱 연산자. - 호환 유지: 화살표 함수(인터프리터 클로저로 실행), 스프레드(배열/객체/호출인자), optional chaining, 템플릿 리터럴(
${}은 같은 인터프리터로 해석), 화이트리스트 생성자의new(new Date(...)·Array.from(new Set(...))등 —Date/Set/Map/WeakSet/WeakMap/Array등만 허용,new Function은 차단), 화이트리스트 전역(Math/JSON/Date/Array/Object/Number/String/Boolean/parseInt/parseFloat/isNaN/isFinite) 및 인스턴스 메서드 호출. - 컨텍스트 값이 항상 전역보다 우선한다. 미존재 식별자는
with(ctx)시맨틱대로undefined를 반환한다(예외 없음).
scripts[].src 원격 스크립트 차단 + 신뢰 출처 허용목록 (KVE-2026-1915 B-2)
TemplateApp.loadLayoutScripts— 레이아웃 스크립트src는 same-origin path-only(/시작)만 로드한다.//(protocol-relative)·scheme 포함 절대 URL(외부 origin)은 skip + 경고로 원격 코드 로드를 차단한다.- 예외: 확장이 manifest(
trusted_script_hosts)로 선언해 코어가window.G7Config.trustedScriptHosts로 노출한 신뢰 호스트의 외부 스크립트는 허용한다(isAllowedScriptSrc). CKEditor5(cdn.ckeditor.com)·Daum 우편번호(t1.daumcdn.net) 등 번들 확장의 CDN 스크립트가 정상 로드되도록 하되, 편집기 저장분에 임의 원격 스크립트를 넣는 경로는 여전히 차단한다. (신뢰 경계: 미선언 외부 origin 은 항상 skip)
[engine-v1.58.4] - 2026-08-14
Fixed
레이아웃 렌더링 테스트 하네스가 named_actions 를 등록하지 않던 문제
__tests__/utils/layoutTestUtils.ts::createLayoutTest— 레이아웃 최상위named_actions를ActionDispatcher.setNamedActions()로 등록한다. 기존에는 등록 단계가 없어actionRef참조가 해석되지 않았고, 그 액션은 경고만 남기고 아무 일도 하지 않은 채 테스트가 통과했다.- 하네스 자신이 검사 대상의 일부라, 이 누락은
named_actions를 쓰는 레이아웃의 테스트를 통째로 무력화한다 — 액션이 붙지 않았는데도 렌더 단언은 전부 초록이므로 결함이 드러날 통로가 없다. setNamedActions는 선택 호출(?.)이다 — 이 API 가 없는 디스패처 스텁으로 만든 기존 테스트가 깨지지 않게 한다.
[engine-v1.58.3] - 2026-08-14
Fixed
교차 출처(공개 자산 CDN) 요청에 세션 토큰이 실리고 이미지가 통째로 실패하던 문제
api/ApiClient.ts::setupInterceptors— 요청 인터셉터가Authorization을 동일 출처 요청에만 첨부한다. 판정은 신설isCrossOriginRequest()가 담당하며, 상대 경로는 항상 동일 출처, 절대 URL(https://…)과 프로토콜 상대 URL(//host/…)만window.location.origin과 비교한다(파싱 실패·SSR 은 동일 출처로 간주해 기존 동작 유지).- 공개 자산 디스크(S3/CDN) 옵트인이 켜지면 첨부·이미지의
download_url이 외부 origin 절대 URL 이 된다. 종전 인터셉터는 URL 출처를 보지 않고 토큰을 붙였으므로 두 가지가 동시에 발생했다.- 토큰 노출 — 관리자 세션 토큰이 제3자 CDN origin 으로 전송된다. CDN 이
Access-Control-Allow-Origin을 허용하는 흔한 구성에서는 요청이 성공하므로 그 origin 의 접근 로그에 토큰이 남는다. - 이미지 전면 실패 —
Authorization은 CORS 안전목록 밖이라 preflight 가 발생한다. 버킷/배포에 CORS 규칙이 없으면(AWS S3 기본값) preflight 가 거절되어 그 이미지가 전부 깨진다.
- 토큰 노출 — 관리자 세션 토큰이 제3자 CDN origin 으로 전송된다. CDN 이
- 실측(AWS S3 실 버킷, 관리자 상품 이미지 탭): 요청 헤더에
authorization: Bearer …가 실린 채net::ERR_FAILED+ 콘솔 CORS 오류 15건. 같은 이미지를<img>로 여는 상점 화면은 CORS 대상이 아니라 정상이어서, 관리자 화면에서만 증상이 나타난다. - 로컬
public디스크(동일 출처)나 CORS 를 허용하는 개발용 오브젝트 스토리지에서는 재현되지 않는다 — 두 조건(외부 origin + CORS 미허용)이 겹쳐야 드러난다.
[engine-v1.58.2] - 2026-08-10
Fixed
target: "_local.xxx" 로 기록한 값을 커스텀 핸들러가 읽지 못하던 문제
ActionDispatcher.ts::handleSetState— dot notation 경로(target: "_local.paymentMethod")로 기록한 값을 canonical source 인_global._local에도 동기화한다. 기존에는context.setState(ReactlocalDynamicState)만 갱신하고_global._local은 갱신하지 않았다. 모듈·플러그인 커스텀 핸들러가 상태를 읽는 공개 통로인G7Core.state.getLocal()은_global._local을 읽으므로, dot notation 으로 기록된 값은 핸들러에게undefined로 보였다.- 화면은 정상으로 보인다 — 선택 표시는 React 저장소만으로 그려지기 때문이다. 예외도 경고도 콘솔 출력도 없이 핸들러가 받는 값만 비어 발견이 늦다. 실제 발현: 주문서형 결제에서 간편결제(네이버페이 등)를 고르고 결제하면 PG 플러그인이 선택값을 못 읽어 간편결제 자체창 대신 통합결제창이 열렸다.
target: "local"형태는 engine-v1.50.0 에서 이미_global._local을 동기화하고 있었다. 같은 뜻의 두 표기가 서로 다른 저장소에 쓰이던 비대칭을 해소한 것이다.- 동기화 기준은 현재
_global._local+ 변경된 최상위 키다.target: "local"경로처럼 전체 스냅샷을 넘기지 않는다 — 클릭된 리프 컴포넌트의 부분 상태가 base 가 되면_global._local의 다른 키를 잃거나, 앞서 커스텀 핸들러가setLocal()로 기록한 값을 되돌릴 수 있다. __g7PendingLocalState는 갱신하지 않는다._global._local기반 전체 스냅샷을 pending 에 넣으면 DataGridexpandedRows처럼 React 에만 존재하는 상태를 초기값으로 덮어쓴다. 전역 상태 대입은 동기적이라 pending 없이도 같은 tick 의getLocal()이 최신값을 읽는다.scope: "parent" | "root"는 dot notation 분기가 구현하지 않는 타깃이므로 동기화 대상에서 제외한다(모달에서 부모 스코프를 노린setState가 페이지 저장소를 오염시키는 것 방지).- 저장소 영향 범위(전수 확인):
target: "_local.xxx"사용은 8건이며 전부sirsoft-basic체크아웃 3개 파일이다(결제수단 선택·무통장 은행 선택·환불계좌 초기화·주문 제출 플래그). TS/TSX 코드에서 이 형태로 dispatch 하는 곳과$parent._local.xxx/$root._local.xxx사용은 0건이다.
[engine-v1.58.1] - 2026-08-07
Fixed
event 키로 적은 DOM 이벤트가 핸들러로 연결되지 않던 문제
ActionDispatcher.ts::bindActionsToProps— 액션의event값이 알려진 DOM 이벤트 이름이면 React prop 이름으로 정규화한다(click→onClick). 기존에는type만 매핑을 거치고event는 값이 그대로 prop 이름이 되어,event: "click"이props.click을 만들었다. React 는 그런 prop 을 무시하므로 예외도 경고도 콘솔 출력도 없이 핸들러가 붙지 않은 채 렌더됐다 — 버튼이 눌리기는 하는데 아무 일도 일어나지 않는다.- 규정은 액션 이벤트 키를
type또는event로 적을 수 있다고 명시하므로, 어느 쪽으로 적었는지에 따라 동작이 갈리면 안 된다. 두 경로가 같은 매핑 표(DOM_EVENT_PROP_MAP)를 공유하도록 통일했다. - 정규화는 알려진 DOM 이벤트 이름에만 적용한다. 이미
onXxx형태인 컴포넌트 콜백(onSortEnd)과 확장이 발행하는 네임스페이스 이벤트(upload:board_attachments,notification.received등)는 손대지 않는다 — 일괄로 접두사를 붙이면 그 이벤트들이 통째로 끊긴다. - 부수 효과로
event: "drop"/"dragover"도 자동preventDefault가드에 정상 편입된다(그 가드는onDrop/onDragOver이름으로 판정한다). - 저장소 영향 범위(전수 확인): DOM 이벤트 이름을
event로 적은 곳은 2건이며 둘 다 이 결함으로 동작하지 않고 있었다 — 관리자 주문상세의 현금영수증 "발급 이력" 아코디언(click), 게시판 글쓰기 폼의 카테고리 선택(change). 나머지event사용처는 모두 네임스페이스 커스텀 이벤트라 동작 변화가 없다.
event 경로에서 커스텀 컴포넌트 이벤트의 $event payload 가 사라지던 문제
ActionDispatcher.ts::bindActionsToProps—event로 적은 액션도type경로와 같은 규칙으로 이벤트 객체를 해석한다(표준 DOM 이벤트 > 커스텀 컴포넌트 이벤트 synthetic 승격 > 빈 이벤트). 해석 로직을resolveEventForHandler()한 곳으로 모았다.- 합성 컴포넌트(Select·MultilingualInput 등)는
preventDefault없는{ target: { name, value } }를 emit 한다. 기존event경로는 이를 빈Event('custom')으로 갈아끼워$event.target.value가undefined가 됐고, 액션은 success 로 기록되는데 저장되는 값만 비는 상태가 됐다 — 콘솔·네트워크 어디에도 흔적이 없어 발견이 늦다. - 저장소 영향 범위(전수 확인): 현재
event로 바인딩된 지점 중 이 수정으로 동작이 달라지는 곳은 없다. 합성 컴포넌트(Select·Toggle·TagInput·HtmlEditor)가 모두preventDefault를 실어 emit 하고 있고,preventDefault없이 emit 하는 CodeEditor 는 유일한event바인딩 지점에debounce가 걸려 있어 이미 승격 로직을 타고 있었다. 즉 이 항목은type경로에만 있던 규칙을event경로에도 맞춘 예방적 통일이다 — 규정이 명시한 컴포넌트 계약({ target: { value } })을 그대로 따르는 컴포넌트를 debounce 없이event로 바인딩하는 순간 종전 코드에서는 값이 비게 된다. _changedKeys메타데이터는 승격 시에도 보존한다(디바운스 병합이 이 값을 쓴다).- raw value 를 그대로 넘기는 콜백(
onChange(value))은event경로에서도 빈 이벤트를 유지한다 — 마운트 시점 콜백까지 값으로 승격하면 API 로드 데이터를 초기값으로 덮어쓰는 기존 회귀가 재발한다.
[engine-v1.58.0] - 2026-08-07
Added
편집기 상태 scope — 선택 세그먼트 토큰 /*?
matchStateScope.ts::matchRoutePattern—scope.match에/*?(0개 또는 1개 세그먼트) 토큰을 추가했다. 0개일 때는 앞 슬래시까지 함께 접힌다.- 배경: 라우트 접두사가 운영자 설정이면 세그먼트가 있을 수도 없을 수도 있다. 상점 주소는
route_path로 바꿀 수 있고(/store/products)no_route를 켜면 세그먼트가 아예 없다(/products). 기존*는 정확히 한 세그먼트라 후자를 표현할 수 없어, 그런 사이트에서는 상태 그룹이 매칭되지 않고 캔버스 상태 토글이 예외·경고 없이 사라졌다. - 치환 순서 주의: 정규식 이스케이프가
?를\?로 바꾸므로 선택 세그먼트는 이스케이프 후 형태(/*\?)를 먼저 치환하고, 그 뒤 남은*를 한 세그먼트로 바꾼다. 순서를 뒤집으면/*?의*가 먼저 소비된다. - 기존 패턴 호환: 치환은
/*?형태에만 반응하므로?를 쓰지 않는 패턴의 결과 정규식은 종전과 동일하다(저장소 내 route scope 패턴 중?사용 0건 — 전수 확인).
[engine-v1.57.0] - 2026-08-06
Added
버전 이력 목록 조회 상한 + '더 보기'
useLayoutVersions.ts— 버전 목록 조회에limit을 붙이고, 한 묶음(VERSION_PAGE_SIZE100)이 가득 찼을 때만 '더 보기' 를 노출한다. 버전 행은 저장할 때마다 쌓이고 정리되지 않아, 상한이 없으면 오래 편집한 레이아웃 하나가 수백~수천 건을 한 응답에 담았다.- 더 보기는 이어붙이기가 아니라 상한을 한 묶음씩 넓혀 재조회한다. 서버가 최신순 상한 조회만 지원하므로 append 하면 경계에서 중복·누락이 생긴다.
- 넓히기는 서버 상한(
VERSION_MAX_LIMIT500)에서 멈춘다. 클램프가 없으면 상한을 넘는 순간 목록 전체가 422 가 되어 이미 보고 있던 이력까지 사라진다. 상한에 도달하면 '더 보기' 를 감춘다. - 두 상수는 서버
LayoutVersionListRequest의DEFAULT_LIMIT/MAX_LIMIT과 같아야 하며, 그 일치는tests/Unit/LayoutVersionLimitParityTest.php가 검사한다. 한쪽만 바꾸면 테스트가 깨진다. VersionHistoryModal.tsx— '더 보기' 버튼(g7le-version-history-load-more) + 조회 중 비활성 처리.
[engine-v1.56.4] - 2026-08-04
Fixed
- 반복 렌더 경로(
renderItemChildren)가 리터럴 단일 바인딩({{true}}/{{false}}/{{null}}/{{undefined}})을 경로로 탐색해 값이undefined가 됐다. 판정 통일(engine-v1.55.0)에서 리터럴을BindingShape한 곳으로 모았지만 이 경로만hasPipes → isComplexExpression → 경로 탐색3분기를 직접 갈라 리터럴을 몰랐고, 그 결과 같은{{true}}가 조건 자리에서는true, 목록 셀·카드 등 반복 렌더 prop 자리에서는undefined로 갈렸다 — 통일이 없애려던 비대칭이 이 지점에만 남아 있었다. 형태 판정을resolveSingleBindingValue에 위임해 다른 경로와 같은 규칙을 쓴다. - 저장소 레이아웃의 리터럴 단일 바인딩 60건은 전부
if/condition자리라 화면 표시에는 변화가 없다. 브라우저 실측으로 발견했고(배포 번들의G7Core.renderItemChildren직접 호출), 같은 지점이 빈 바인딩({{}})도 몰라 경로 탐색으로 보내던 것을 함께 봉인했다.
[engine-v1.56.3] - 2026-08-02
Fixed
- 반복 렌더 경로(
renderItemChildren)가raw:바인딩의 번역 면제 마커를 벗기지 않고 React 로 넘겨, 화면에 Unicode Noncharacter 두 글자(U+FDD0/U+FDD1)가 그대로 실렸다. 마커는 번역 패스가 raw 값을 건너뛰게 하려는 내부 표식이므로 값이 화면으로 나가기 전에 제거돼야 한다. 단발 렌더 경로는resolveTranslationsDeep안에서 벗기지만 반복 경로에는 그 패스가 없었다. rawMarkers.stripRawDeep()을 추가하고 반복 경로의 React 경계(props / children / 컴포넌트 id) 전부에 적용했다. 마커가 없으면 원본 참조를 그대로 돌려주므로 반복 렌더에서 추가 할당이 생기지 않는다. 배열·객체 리프까지 재귀하되 이미 렌더된 React 엘리먼트는 순회하지 않는다.
[engine-v1.56.2] - 2026-08-02
Fixed
DataBindingEngine.evaluateExpression— 컨텍스트에 함수 파라미터 이름이 될 수 없는 키(sales_status[],data-id, 예약어 등)가 하나라도 있으면 평가 함수 생성 자체가 SyntaxError 로 실패해, 그 키를 쓰지 않는 식까지 같은 컨텍스트에서는 전부 평가되지 못했다. 이제 그런 키를 파라미터 목록에서 제외한다. 제외해도 잃는 것은 없다 — 식 안에서 맨이름으로 참조할 수 없는 키였고, 실제 작성은query['sales_status[]']처럼 상위 객체를 거치므로 그대로 동작한다.- 증상은 예외나 화면 오류가 아니라 값이 비어 보이는 것이었다(평가 실패 → 폴백). 판정 통일(engine-v1.55.0) 이후 이 경로로 들어오는 식이 늘면서 노출 범위가 넓어졌다.
[engine-v1.56.1] - 2026-08-02
Added
G7Core.evaluateCondition(condition, extraContext?)— 템플릿 컴포넌트가 조건 문자열을 엔진과 같은 규칙으로 평가할 수 있게 노출한다. 전역/로컬/계산 상태를 컨텍스트에 병합해 넘기므로,{{_local.expanded}}같은 조건도 컴포넌트에서 그대로 쓸 수 있다.
Changed
TranslationEngine의$t:파라미터 값 평가가new Function대신DataBindingEngine.evaluateExpression을 쓴다. 종전 자체 평가기는$localized(...)·$t(...)·$uuid()헬퍼와 optional chaining 전처리, 표현식 함수 캐시를 쓰지 못했고 실패 시 조용히 빈 문자열을 돌려줬다 — 번역 문구에서 값만 사라지므로 원인 파악이 어려웠다.$t:key|name=값의|는 파라미터 구분자이므로 이 자리에서는 파이프를 지원하지 않는다(별개 문법 영역). 파이프 분기 스캔의 면제 목록에 사유와 함께 등재했다.
[engine-v1.56.0] - 2026-08-02
Fixed
iteration 과 if 를 함께 쓴 목록이 통째로 렌더되지 않던 문제
DynamicRenderer—if가 항목 변수({{user.is_active}}등)를 참조하면, 조건 평가가 iteration 분기보다 먼저 걸리는데 그 시점 컨텍스트에는 항목 변수가 없어 조건이 항상 false 가 됐다. 결과는 목록 전체 미렌더이며 예외도 경고도 남지 않는다. 이제 iteration 이 있으면 부모 시점 조건으로 끊지 않고, 항목별 컨텍스트에서 자식이 같은if를 다시 평가한다. 같은 작성이 반복 렌더 경로(cellChildren등)에서는 정상 동작했기 때문에 재현 위치를 특정하기 어려운 결함이었다.- 외곽 변수만 참조하는
if는 결과가 종전과 같다(항목 수만큼 재평가되지만 렌더 사이클 캐시가 흡수한다).
반복 렌더 경로에서 문자열 중간의 $t: 가 번역되지 않던 문제
RenderHelpers.renderItemChildren— 종전에는$t:로 시작하는 문자열만 번역해서"27$t:admin.count_suffix"같은 중간 토큰이 원본 키 그대로 화면에 노출됐다(반복 서브트리 내 실사용 2건). 일반 컴포넌트 경로는 이미 처리하던 형태다. JSON 구조 문자열({...}/[...]) 안의$t:는 데이터의 일부이므로 번역하지 않는 가드도 함께 이식했다.- 이 처리는
$t:defer:분기보다 뒤에 둔다 —$t:defer:는 이 경로가 항목 컨텍스트와 함께 처리하도록 남겨 둔 것이고, 반복 서브트리 내 실사용 23건이 그 순서에 의존한다. - 표현식 평가 결과가 배열·객체인 경우에도 리프 문자열의
$t:까지 번역한다. 종전에는 문자열 결과만 검사해 배열 안의 키가 번역되지 않았다.
Added
- 반복 렌더 경로가
$switch/$cases객체를 값으로 해석한다. 종전에는 객체가 그대로 컴포넌트에 전달되어 조용히 아무 일도 일어나지 않았다(일반 경로는 지원). 평가는DataBindingEngine.resolveSwitch에 위임한다. - 반복 렌더 경로도 액션 정의 객체를 선평가하지 않는다. 콜백 prop 에 담긴 액션 정의를 렌더 시점에 해소하면 내부
{{_local.xxx}}가 그 시점 값으로 고정되어, 실행 시점의 최신 상태를 보지 못한다. DynamicRenderer의 iteration 에{item_var}_index자동 변수를 주입한다(반복 렌더 경로에는 이미 있던 것).
두 Added 항목과 _index 주입은 저장소 실사용 0건으로, 같은 작성이 경로에 따라 다르게 동작하는 것을 없애기 위한 정렬이다.
[engine-v1.55.1] - 2026-08-02
Fixed
보간 문자열의 바인딩 탐색을 정규식에서 스캐너로 교체
DataBindingEngine.resolveBindings— 바인딩 위치를BindingShape.scanBindings()로 찾는다. 종전 정규식(\{\{([^}]+)\}\})은 식 안에}가 들어가면 매칭하지 못했고,String.replace는 매칭이 없을 때 입력을 그대로 돌려주므로"a {{x ?? {}}} b"같은 문자열이 원본{{...}}그대로 화면에 노출됐다. 스캐너는 따옴표와 중괄호 깊이를 추적하며, 닫히지 않은{{는 종전처럼 리터럴로 남긴다.
표현식 하나의 실패가 컴포넌트의 props 해석 전체를 중단시키던 문제
DataBindingEngine.resolveObject— key 단위로 실패를 격리한다. 이 메서드에는 상위 catch 가 없어서, 표현식 하나가 던지면 그 컴포넌트의 props 가 통째로 해석되지 않았다(중첩 prop 표현식은 특히 렌더 도중 throw 로 이어졌다). 이제 실패한 key 만undefined가 되고 경고가 남으며, 나머지 key 와 형제 컴포넌트는 정상 렌더된다. 중첩 객체는 재귀 호출이 같은 규칙으로 처리하므로 실패가 가장 안쪽 key 에서 멈춘다.
Changed
DynamicRenderer의 중첩 객체 해석에도 컴포넌트별skipBindingKeys를 전달한다. 종전에는 전달하지 않아 코어 기본 4개만 적용됐고, 컴포넌트가 "내가 직접 반복 렌더한다" 고 선언한 키가 중첩 위치에서는 선평가되어 항목 컨텍스트 없이 stale 값으로 고정될 수 있었다. 저장소 전수 스캔 결과 해당 위치는 0건이라 예방 목적의 변경이다(선언된 키:DataGrid·CardGrid·RichSelect·DropdownButton).
[engine-v1.55.0] - 2026-08-02
Changed
바인딩 식의 형태 판정을 단일 모듈로 통일
같은 작성 문법이 어느 렌더 경로를 타느냐에 따라 다른 결과를 내던 원인은 판정 로직이 복제되면서 복사본마다 인식 문자 집합이 갈라졌기 때문이다. "이 문자열이 단일 바인딩인가" 를 판정하는 구현이 4종 + 정규식 4종, "이 식이 표현식인가 경로인가" 를 판정하는 정규식이 6종 있었다. template-engine/BindingShape.ts 로 판정을 모으고 14개 지점이 이를 쓰도록 바꿨다.
통일한 것은 판정뿐이다. 캐시 정책·예외 처리·DevTools 추적·후처리(번역, raw 마커)는 지점마다 정당한 이유로 다르므로 그대로 두었다.
-
단일 바인딩 판정: 따옴표(
'"`) 안의 중괄호를 세지 않고, 따옴표 안의 백슬래시 이스케이프를 소비하며, 중괄호 균형을 추적한다. 종전 greedy 정규식(^\{\{(.+)\}\}$)은"{{a}}-{{b}}"같은 보간 문자열까지 단일 바인딩으로 오판했다 — 액션 경로는 덧댄 가드로 막고 있었지만 데이터소스 params 와initLocal/initGlobal값 추출에는 그 가드가 없었다. -
표현식/경로 판정: 종전 6개 방언 중 최대집합을 정본으로 삼았다. 좁은 방언은
query['sales_status[]']같은 따옴표 키 인덱싱과 배열/객체 리터럴을 "단순 경로" 로 오판해 경로 탐색으로 보냈고, 결과는 조용한undefined였다. 저장소 전수 스캔 기준 63개 식의 라우팅이 바뀐다(따옴표 대괄호 22, 숫자 인덱싱 39, 기타 2). 숫자 인덱싱은 두 경로의 결과가 동치임을 4개 컨텍스트(존재/범위밖/null/중첩 미정의)로 확인했고, 따옴표 대괄호 22건은 지금까지undefined였던 값이 실제 값으로 나타난다. -
빈 바인딩(
{{}}) 봉인:undefined+ 경고로 처리한다. 종전에는 빈 경로가 경로 탐색으로 들어가 컨텍스트 객체 전체가 값으로 반환됐고, 그것이 문자열로 서식되면 전역 상태가 통째로 화면이나 요청에 실릴 수 있었다. 안전망으로resolvePath진입 시에도 빈 경로를 차단한다. 레이아웃 실사용은 0건이며 런타임에 조립되는 문자열에서 발생할 수 있는 경로다. -
리터럴(
true/false/null/undefined) 처리도 한 곳으로 모았다. 종전에는 조건 두 지점만 리터럴을 알아{{true}}가 prop 자리에서는 경로로 탐색돼undefined가 됐다. 저장소의 리터럴 단일 바인딩 60건은 전부if/condition자리라 실제 표시에는 변화가 없다. -
선행 하네스:
__tests__/BindingShape.routingParity.test.ts— 저장소의 모든 레이아웃 JSON 에서 단일 바인딩을 수집해 구 방언들과 신 정본의 판정을 대조하고, 판정이 바뀌는 식 전체를 소속 파일과 함께 스냅샷으로 고정한다. 새 레이아웃이 라우팅 변경 대상 식을 도입하면 이 테스트가 깨진다.
[engine-v1.54.10] - 2026-08-02
Fixed
파이프(|)를 해석하지 않고 JS 비트 OR 로 평가하던 지점 8곳
{{식 | 파이프}} 를 evaluateExpression 으로 보내면 | 가 JS 비트 OR 로 평가된다. 인자 있는 파이프(date('YYYY-MM-DD'))는 함수가 아니라며 예외를 던지고, 인자 없는 파이프(| number)는 날짜 문자열이 0 이 되는 식의 조용한 오답이 된다. engine-v1.54.3 이 렌더 경로를 정리했으나 아래 지점들은 남아 있었다. 모두 hasPipes() 선분기 + evaluatePipeExpression 으로 정렬했다.
- 액션 params (
ActionDispatcher) — 예외가 catch 에 걸리면 catch 가 원본{{...}}문자열을 그대로 반환했고, 그 값이 요청 본문·쿼리스트링에 실려 서버로 전송됐다. 화면에는 아무 표시도 나지 않는다. - 데이터소스 params (
DataSourceManager) — 동일. 쿼리스트링에%7B%7B로 인코딩된 리터럴이 실렸다. iteration의source(RenderHelpers.resolveIterationSource) — 결과가 배열이 아니게 되어 반복 대상이 사라진다. 목록 전체가 렌더되지 않으며 예외도 경고도 남지 않는다. DynamicRenderer 와renderItemChildren이 공유하는 함수라 한 곳 수정으로 양 경로가 함께 해소된다.expandContext(G7CoreGlobals.renderExpandContent) — 확장 행에 넘기는 값에 파이프가 적용되지 않았다.- computed 정의식 — 렌더 경로(
DynamicRenderer)와 액션 경로(ActionDispatcher의 setState 직후·커스텀 핸들러 직후 재계산 2곳), 그리고 레이아웃 편집기 미리보기(computedRecipeEngine)까지 네 곳이 같은 결함을 복제하고 있었다. 셋 다 실패 시 "이전 값 유지" 라 computed 가 영원히 갱신되지 않는다. 한 곳만 고치면 같은 computed 가 렌더 직후와 액션 직후에 다른 값이 되므로 함께 정렬했다. slot표현식 (DynamicRenderer) — 평가 결과가 문자열이 아니게 되어typeof result === 'string'가드에 걸려 슬롯 미등록으로 조용히 흡수됐다(그 컴포넌트가 화면에서 사라진다).
blur_until_loaded 는 종전대로 파이프를 지원하지 않는다 — truthiness 게이트라 파이프 결과(대개 비어있지 않은 문자열)가 항상 참이 되어 블러가 영구히 켜진다.
- 회귀 방지선: 표현식 평가 지점을 모집단으로 스캔하는 테스트를 추가했다(
__tests__/pipeBranchParity.test.ts). 엔진 소스에서evaluateExpression호출 지점을 전부 찾아 파이프 분기 가드 유무를 검사하고, 의도적 미지원 지점만 사유와 함께 면제 목록에 둔다. 면제 항목이 코드에서 사라지면 그것도 실패한다. 실제로 이 스캔이 중복 블록 하나(액션 커스텀 핸들러 직후 재계산)를 잡아냈다. - 기존 동작 무영향: 레이아웃 전수 스캔 기준 이 8지점의 파이프 실사용은 0건이다. 논리 OR(
||)과 따옴표 안의|(다국어 파라미터 구분자)는 종전대로 파이프로 인식하지 않는다.
[engine-v1.54.9] - 2026-08-02
Fixed
파이프 평가를 문자열 보간에 위임하면서 생긴 비대칭 3건
engine-v1.54.3 이 파이프 분기를 resolveBindings(\{{식}}`)위임으로 처리하면서, 값이 필요한 지점까지 "문자열을 조립해 보간기에 넣고 문자열을 돌려받는" 경로를 타게 됐다. 평가 자체를DataBindingEngine.evaluatePipeExpression()으로 분리하고, 값이 필요한 6개 호출 지점이 이 메서드를 직접 호출하도록 바꿨다.resolveBindings는 그 결과에formatValue` 를 적용해 종전과 같은 문자열을 돌려준다 — 보간의 계약은 그대로다.
- 식 안에 중괄호가 있으면 원본
{{...}}문자열이 화면에 노출됐다. 위임 대상인BINDING_PATTERN(/\{\{([^}]+)\}\}/g)은}를 포함한 식을 매칭하지 못하고,String.replace는 매칭이 없으면 입력을 그대로 돌려준다.{{(row.meta ?? {}) | json}}같은 식이 평가되지 않은 채 그대로 렌더됐다. 예외도 경고도 남지 않는다. 이제 문자열 조립을 거치지 않으므로 정규식의 한계와 무관하다(BINDING_PATTERN자체의 교정은 별건이다 — 보간 경로에는 아직 남아 있다). _computed파이프가 컴포넌트 간 stale 값을 전파했다.DynamicRenderer의 props 해석에서 파이프 분기만getComputedAwareOptions를 거치지 않아, 다른 분기가skipCache로 회피하던 영구 캐시(30초)에_computed.x가 저장됐다._computed는 컴포넌트마다_local기반으로 재계산되므로, 먼저 렌더된 컴포넌트의 값이 뒤따르는 컴포넌트에 그대로 나타났다. 이제 파이프 분기도 같은 옵션을 쓴다($computedalias 포함).- 파이프 결과가 항상 문자열로 서식됐다.
resolveBindings는 보간이 목적이라formatValue를 적용한다. 그 결과{{row.tags | keys}}가 배열이 아니라"[\"a\",\"b\"]"로,{{row.flags | first}}가false대신"false"(truthy!) 로 prop·조건에 전달됐다. 값이 필요한 지점은 이제 원본 타입을 받는다 — 파이프가 없는 단일 바인딩(resolve)·복합식(evaluateExpression)과 같은 규칙이다.
전환한 호출 지점: DataBindingEngine.resolveObject / DynamicRenderer 의 props·text / ConditionEvaluator.evaluateStringCondition / RenderHelpers 의 evaluateIfCondition·renderItemChildren.
- 실패 정책도 지점별로 정렬했다.
evaluatePipeExpression은 평가 실패를 그대로 던지고, 각 호출 지점이 같은 자리의 복합식 분기와 동일하게 처리한다(경고 후undefined/''/ 조건false). 종전에는 위임 대상이 예외를 삼키고 원본{{...}}문자열을 돌려주어, 조건 자리에서는 그 문자열이 truthy 로 평가돼 반대 분기를 탔다.resolveObject는 상위 catch 가 없으므로 key 단위로 격리한다. - 조건 두 지점(
evaluateStringCondition,evaluateIfCondition)은 파이프 식의raw:접두사를 제거한 뒤 평가한다. 위임 형태에서는resolveBindings가 접두사를 벗겨 줬으나 직접 호출에서는 그 단계가 없다.rawMarkers.stripRawPrefix()로 공통화했다. - 기존 동작 무영향: 레이아웃 전수 스캔 기준 파이프 단일 바인딩은 32건(고유 29)이며 전부 노드
text, 사용 파이프는date/datetime/number로 모두 문자열을 반환한다. 반복 렌더 서브트리·액션 params·데이터소스 params 내 파이프와 중괄호를 포함한 파이프 식은 각각 0건이다.
[engine-v1.54.8] - 2026-08-01
Fixed
_localInit prune 분기가 제거할 것이 없어도 매번 상태 갱신 + 캐시 무효화를 하던 문제
DynamicRenderer.tsxremoveMatchingLeafKeys— 제거 대상이 없어도 경로상의 객체를 항상 새로 만들어 반환했다. 호출부는 참조 비교로 "실제로 제거된 것이 있는가" 를 판정하므로, 중첩 경로가 겹치기만 하면(예: 저장소 A 에form.theme, payload 에form.auto_cancel_days) 내용이 동일한데도 판정이 참이 되어setLocalDynamicState와invalidateCacheByKeys(['_local'])가 상시 실행됐다. engine-v1.54.7 이 "실제 제거된 경우에만" 무효화하도록 명시한 조건이 사실상 항상 참이었던 셈이다.- 수정: 제거가 하나도 없으면 원본 참조를 그대로 반환한다. 제거가 일어난 경우에도 변경되지 않은 형제 가지는 참조를 보존하므로, 불필요한 하위 리렌더도 함께 줄어든다. 다른 호출 지점(
setLocal정리 경로)도 같은 이득을 받으며, 반환값을 변형하는 호출부는 없다(전수 확인). - prune 분기의 상태 쓰기를 updater 형태(
prev => ...)로 바꿨다. 같은 commit 에서useLayoutEffect가 큐에 넣은 제거가 아직 반영되기 전일 수 있어, 커밋 시점 스냅샷을 직접 쓰면 그 제거를 되살릴 수 있었다. 캐시 무효화 판정은 종전대로 커밋된 값 기준 동기 계산이다(updater 안의 플래그는 StrictMode 이중 호출·배치 지연으로 신뢰할 수 없음). - 동작 계약 무변경: 제거 대상이 실제로 있을 때의 결과 상태·무효화·병합 우선순위는 이전과 동일하다.
[engine-v1.54.7] - 2026-08-01
Fixed
탭 왕복 후 먼저 편집한 입력칸에 옛 입력값이 남아 화면과 실제 저장값이 어긋나던 문제
DynamicRenderer.tsx_localInituseEffect — 적용 여부를 전역 해시(__g7LocalInitTracking)만으로 판정했다. 그런데 실제 리셋 대상인localDynamicState(저장소 A)는 렌더러 인스턴스별이라, 먼저 effect 가 도는 인스턴스가 전역 토큰을 소비하면 나머지 루트 렌더러의 저장소 A 는 영원히 리셋되지 않았다. 병합은 A 우선(deepMergeState(dataContext._local, dynamicState))이므로 갱신된 저장소 B 위에 stale A 가 덮였다.- 증상: 폼 데이터소스가
initLocal+refetchOnMount: true이고 탭 전환이 URL 을 바꿔 remount + refetch 를 유발하는 화면에서, 되돌아오기 전 입력칸을 2개 이상 편집하면 먼저 편집한 칸에 사용자가 친 값이 남는다(마지막 편집 칸은 정상 복귀). 저장값은 서버값이라 화면 표시와 실제 값이 어긋나며, 새로고침해야 드러난다. 콘솔 에러 0건의 조용한 실패다. - 재현은 SPA 라우팅(탭 클릭)에서만 된다. 주소창 이동·새로고침은 모든 렌더러를 새로 마운트하고 전역 추적도 초기화하므로 수정 전에도 증상이 나타나지 않는다 — E2E 로 이 계열을 잡을 때 전체 새로고침 왕복을 쓰면 false green 이 된다(실측).
- 측정 범위: "먼저 편집"은 필요조건이지 충분조건이 아니다. 통제 실험에서 A(먼저)→B(나중) 순서는 A 가 어긋났고, 순서를 뒤집은 B(먼저)→A(나중) 는 둘 다 정상이었다. 잔존이 화면까지 드러나려면 그 입력칸을 소유한 렌더러 인스턴스가 저장소 A 리셋을 건너뛴 쪽이어야 하기 때문이다.
- 순서 의존성의 출처: 자동바인딩
performStateUpdate는 키입력마다 병합된_local전체 스냅샷을 저장소 A 에 쓰는데, 뒤따르는removeMatchingLeafKeys정리는__g7SetLocalOverrideKeys에 남은 마지막 leaf 만 지운다. 이 전역 플래그는queueMicrotask로 클리어되므로 다음 필드를 칠 때 직전 필드 키는 이미 사라져 있다. - 수정: 판정을
localInitSlot.ts의resolveLocalInitAction으로 분리하고apply/prune/skip3분기로 확장했다. 다른 인스턴스가 이미 적용한 payload 를 만난 인스턴스는 자기 저장소 A 에서 payload 키 공간만 제거한다 — 값을 다시 쓰지 않는다. 제거하면 그 자리에 이미 갱신된 저장소 B 가 그대로 비쳐 보인다. - 재적용이 아니라 제거인 이유: 늦게 마운트된 인스턴스가 소비된 과거 payload 를 저장소 B 에 되쓰면 그 사이의 사용자 편집이 되돌아간다(
mergeLocalInitSlot의consumed → 교체규칙이 막고 있는 회귀). 제거는 값을 도입하지 않으므로 이 위험이 구조적으로 없고, 저장소 A 가 비어 있는 신규 마운트 인스턴스에서는 no-op 이다. - 실제로 키가 제거된 경우에만
invalidateCacheByKeys(['_local'])를 호출한다. 병합 결과가 바뀌는데 단순 경로 바인딩은 캐시 대상이라 무효화하지 않으면 화면이 갱신되지 않고, 반대로 무조건 무효화하면 불필요한 재평가가 생긴다. - 기존 동작 무영향: 전역 추적 구조·자동바인딩 쓰기 경로(
performStateUpdate)·병합 우선순위·리렌더 전략 어느 것도 변경하지 않는다._localInitpayload 가 없는 화면은 분기에 진입하지 않는다.
[engine-v1.54.6] - 2026-08-01
Fixed
통신이 끊겼을 때 내부 식별 문구가 사용자에게 노출되던 문제
template-engine/ActionDispatcher.ts— 액션 실패 문구 결정을resolveActionFailureMessage()로 분리하고, 응답 자체가 없었던 실패(TypeError: Failed to fetch/AbortError)에는 다국어 안내($t:core.errors.network_request_failed)를 쓴다. 종전에는 서버 메시지가 없으므로 내부 식별 문구인Failed to execute action: apiCall이 그대로 토스트에 떴다 — 운영자가 무슨 일이 일어났는지 알 수 없고 다국어도 적용되지 않았다.- 우선순위는 (1) 서버 메시지 → (2) 네트워크 안내 → (3) 기존 문구다.
ActionError가 이미 들고 있던 고유 메시지(예: 미등록 핸들러 안내)는 그대로 보존한다. {{error.message}}가 상태에 실려 텍스트로 렌더되는 경로가 있어$t:키를 디스패처에서 미리 번역한다(키 문자열 노출 방지).- 코어 다국어 키
core.errors.network_request_failed추가 (ko/en + ja 번들).
[engine-v1.54.5] - 2026-07-31
Fixed
데이터소스 initLocal 동기화가 라우트 진입 시드를 되돌리던 문제
template-engine/DynamicRenderer.tsx—_localInit을 전역_local(저장소 B)에 동기화할 때, 병합 base 를 렌더 시점 스냅샷(dataContext._global._local)이 아니라 쓰기 시점의 canonical 상태(함수형 업데이트의prev._local)로 바꿨다.setGlobalState는{ _local: X }를 얕게 펼쳐 저장소를 통째로 교체하는데, 이 effect 는 렌더 커밋 뒤에 실행되므로 그 사이에init_actions가 URL query 로 시드한 값이 스냅샷 기반 교체에 통째로 되돌아갔다.- 증상: 목록 화면에서 필터를 적용하고 상세로 들어갔다가 브라우저 뒤로가기로 복귀하면 URL·목록·총건수는 필터가 걸린 상태인데 필터 컨트롤만 기본값으로 표시됐다. 그 상태에서 다른 조건을 바꿔 검색하면 기존 필터가 조용히 사라진다.
initLocal데이터소스의 응답 시점과 시드 시점의 경합이라 같은 화면에서도 재현이 들쭉날쭉했다. TemplateApp.ts—dataContext._globalSetState가 함수형 업데이트를 그대로 위임하도록 시그니처를 넓혔다(setGlobalState는 이미 함수형을 지원)._merge: "replace"계약(기존 base 무시)과_local이외 전역 키 보존은 종전과 동일하다.
[engine-v1.54.4] - 2026-07-31
Fixed
노드 text 키의 {{raw:...}} 바인딩이 평가되지 않고 텍스트가 통째로 사라지던 문제
DynamicRenderer.tsxrenderChildren— 노드 레벨text키 처리에서raw:접두사를 벗기지 않은 채 표현식 평가로 넘겨,raw:의 콜론이 식의 일부로 파싱되며Unexpected token ':'로 예외가 났다. props 값 해석(renderProps)과 반복 렌더(RenderHelpers.renderItemChildren)에는 이미 있던 접두사 처리가 이 경로에만 빠져 있었다.- 증상: 관리자 레이아웃 편집 화면의 파일 목록 등
text: "{{raw:...}}"를 쓰는 자리에서 텍스트가 빈 값으로 렌더된다. 콘솔에는 경고만 남고 에러 화면이 뜨지 않아 조용한 실패로 관측된다(해당 화면 1회 진입에 174건). - 접두사 제거 후 파이프/복합식/단순 경로 분기를 동일하게 태우고, 결과는
wrapRawDeep로 감싸 번역 면제를 유지한다 — 다른 두 경로와 같은 규칙이다. - 기존 동작 무영향:
raw:가 없는text는 종전 경로·결과 그대로다.
[engine-v1.54.3] - 2026-07-25
Fixed
DataGrid cellChildren 등 반복 렌더 컨텍스트에서 단일 바인딩의 파이프가 적용되지 않던 문제
RenderHelpers.tsrenderItemChildren— 단일 바인딩 판정 후 파이프(|) 분기를 추가했다. 종전에는|가 복잡 표현식 문자로 분류되어evaluateExpression으로 라우팅되었고, JS 비트 OR 로 평가되어 인자 있는 파이프({{row.created_at | datetime('YYYY-MM-DD HH:mm')}})는 예외로 값이 사라지고, 인자 없는 파이프({{row.code | uppercase}})는0같은 조용한 오답이 되었다. 같은 표현식이 일반 컴포넌트의text에서는 정상 동작해 재현 위치를 특정하기 어려웠다. (#87 @glitter-gim 님께서 제보해주셨습니다.)- 적용 범위: DataGrid 의
cellChildren·카드 뷰, CardGrid 의 카드 children,expandChildren,footerCells등 반복 렌더 경로 전체. 컴포넌트의text와 props 값 모두 동일하게 해석된다. - 같은 결함이 있던 나머지 단일 바인딩 경로도 함께 정렬했다 —
DynamicRenderer.tsx의 props 해석(예외가 나면 해당 prop 이undefined로 떨어짐),DataBindingEngine.tsresolveObject(예외가 밖으로 전파되어 그 컴포넌트의 props 해석 전체가 중단),ConditionEvaluator.tsevaluateStringCondition과RenderHelpers.tsevaluateIfCondition(if조건 오판정). blur_until_loaded는 의도적으로 제외한다 — truthiness 게이트라 문자열 결과가 항상 참이 되어 블러가 영구히 켜질 수 있다. 값 서식용인 파이프를 쓰는 자리가 아니다.- 기존 동작 무영향: 파이프가 포함된 단일 바인딩에서만 경로가 바뀐다. 논리 OR(
||)·따옴표 안의|(다국어 파라미터 구분자)는 파이프로 인식하지 않으며, 파이프 없는 단일 바인딩은 종전대로 원본 타입을 유지한다.
[engine-v1.54.2] - 2026-07-24
Fixed
mergeQuery: true 에 query 키가 없으면 병합이 통째로 건너뛰어지던 문제
ActionDispatcher.tshandleNavigate/handleReplaceUrl— 쿼리 처리 진입 조건을if (params.query)에서if (params.query || params.mergeQuery === true)로 넓히고, 병합 대상이 없으면 빈 객체를 넘긴다. 종전에는mergeQuery: true만 적고query를 생략한 액션이 조건문에 걸려 병합 자체를 수행하지 못했고, 그 결과 이동 후 URL 의 쿼리스트링이 전부 사라졌다. 작성자 관점에서 "현재 쿼리를 유지한다" 는 의도를 가장 자연스럽게 표현한 형태가 정반대로 동작하던 함정이다.- 두 핸들러의 게이트를 같은 형태로 유지한다 — 한쪽만 고치면 같은 params 를 써도
navigate와replaceUrl의 결과가 갈린다. - 기존 동작 무영향:
mergeQuery: true+query생략 조합은 저장소 전체에 0건임을 정적 스캔으로 확인했다.mergeQuery: false+query생략은 종전대로 쿼리를 붙이지 않는다.
[engine-v1.54.1] - 2026-07-23
Fixed
끝 슬래시가 붙은 경로(/admin/)가 라우트에 매칭되지 않던 문제
routing/Router.ts—match()진입 시 경로의 끝 슬래시를 정규화(normalizePathname)한 뒤 패턴과 대조한다. 라우트 패턴은 끝 슬래시 없는 형태(*/admin등)로만 정의되므로(routes.json 규약), 종전에는 브라우저가/admin/처럼 끝 슬래시가 붙은 경로로 진입하면matchPattern이 만드는 앵커 정규식(^…/admin$)에 걸리지 않아 어떤 라우트에도 매칭되지 않고 404 로 떨어졌다. 특히 미인증 상태로/admin/에 진입하면 대시보드 리다이렉트 → 로그인 화면 흐름 대신 404 가 노출됐다. 이제 루트(/)를 제외한 경로의 끝 슬래시(연속 슬래시 포함)를 제거해 정상 매칭한다.
[engine-v1.54.0] - 2026-07-20
Added
자산 URL 이중 모드 — 정적 최적화 서버에서의 동작 보장
support/assetUrl.ts(신규) — 동적 엔드포인트 URL 생성을 한 곳으로 모았다.getAssetUrlMode()가window.G7Config.assetUrlMode(서버가 내려주는 초기값) →G7Config.settings.general.asset_url_mode→ 기본값extension순으로 판정하고,suffixed()/templateAsset()/moduleAsset()/pluginAsset()/extensionBundle()/layoutUrl()/layoutPreviewUrl()가 그 모드에 맞는 URL 을 만든다. 서버측App\Support\AssetUrl와 동일 규칙이며, 한쪽만 바꾸면 서버가 만든 URL 과 클라이언트가 만든 URL 이 어긋나 그 자산만 404 가 된다.- 배경: nginx 의 정규식 location(
location ~* \.(js|css|json)$)은 프리픽스 location 보다 먼저 매칭되므로, 확장자 붙은 동적 엔드포인트는try_files ... /index.php폴백이 실행될 기회 없이 nginx 가 직접 파일시스템을 열려 시도해 404 가 된다. aaPanel/CyberPanel/Plesk 기본 템플릿에 들어있는 블록이다. - 확장자 없는 모드의 변환 규칙 — 고정 접미사는 제거(
routes.json→routes), 번들은 접미사가 종류를 구분하므로 세그먼트로 강등(bundle.js→bundle/js), 와일드카드 자산은 경로가 곧 파일명이라 쿼리로 이동(assets/{id}/js/a.js→assets/{id}?file=js/a.js). 마지막 형태가 안전한 이유는 nginx 의 location 정규식이 쿼리스트링을 제외한 경로에만 매칭되기 때문이다. setAssetUrlMode()는 단방향 1회만 허용한다(extension → extensionless). 역방향을 허용하면 양쪽 형태가 모두 실패하는 상황(PHP 다운·WAF 차단)에서 무한 왕복이 된다. 서버 설정은 바꾸지 않는다 — 미인증 클라이언트가 전역 설정을 뒤집을 수 있으면 안 된다.restoreCachedMode()의 localStorage 캐시는 키에cache_version을 포함하고 24시간 TTL 을 둔다. 서버가 정상화된 뒤에도 클라이언트가 옛 모드에 영구 고착되지 않도록 하기 위함이다.
Changed
동적 엔드포인트 URL 생성부 14지점을 빌더 경유로 전환
routing/Router.ts,TemplateApp.ts(4),ComponentRegistry.ts,ErrorPageHandler.ts,LayoutLoader.ts(2),layout-editor/LayoutEditorChrome.tsx,layout-editor/hooks/{useEditorTemplateAssets,useInlineEdit,useLayoutDocument,useExtensionDocument}.ts—routes.json·config.json·components.json·레이아웃 JSON URL 을 모두suffixed()로 생성한다.- 기본 모드(
extension)에서 생성 결과는 치환 이전과 문자열까지 동일하다.suffixed()는 추가 쿼리를v보다 앞에 놓는데(?with_source_meta=1&v=...), 이는 편집기 문서 로드 호출부의 기존 순서를 보존하기 위한 것이다 — 쿼리 순서가 바뀌면 의미는 같아도 URL 문자열이 달라져 HTTP 캐시 키가 갈린다.
[engine-v1.53.1] - 2026-07-12
Fixed
좁은 창에서 레이아웃 편집기가 압착되던 문제
LayoutEditorChrome.tsx— 편집기 셸에 최소 너비(EDITOR_MIN_WIDTH= 1280px)를 부여했다. 레이아웃 편집기는 라우트 트리(280px 고정) + 디바이스 미리보기 캔버스 + 라벨 있는 툴바 버튼 10여 개가 나란히 놓이는 대화면 전용 도구다. 종전에는 셸에 하한이 없어 창을 좁히면 이들이 창 너비에 맞춰 압착됐다. 이제 최소 너비 아래로는 압착하지 않고 부족한 폭을 브라우저 가로 스크롤로 흡수한다(반응형 재배치 아님 — 편집기 UI 는 축약 대상이 아니다).EditorToolbar.tsx— 툴바 직접 자식 전체에flex-shrink: 0+white-space: nowrap규칙을 적용했다. 툴바는 flex row 라 기본값(flex-shrink: 1)에서는 폭이 모자랄 때 각 항목이 깎이고 라벨이 글자 단위로 줄바꿈됐다("요 소 추 가"). 버튼마다 인라인 style 로 붙이는 대신 직접 자식 전체를 덮는 단일 규칙으로 뒀다 — 자체 style 을 가진 하위 컴포넌트(템플릿 전환기·언어 전환기)와 이후 추가될 항목까지 자동으로 포함된다.- 디바이스 미리보기 프레임이 남는 캔버스 폭보다 넓을 때는 종전대로 줌 슬라이더로 축소해 본다(프레임 폭 SSoT 인
deviceList.ts는 변경 없음).
[engine-v1.53.0] - 2026-07-11
Fixed
페이지 로드 요청 1건의 일시 실패가 앱 전체를 죽이던 문제
networkResilience.ts(신규) — 네트워크 레벨 실패에만 지수 백오프로 재시도하는fetchWithRetry/loadScriptWithRetry와 문서 이탈 가드를 제공한다.fetch는 4xx/5xx 로 reject 하지 않고Response.ok=false로 resolve 하므로,TypeError: Failed to fetch는 응답 자체가 없었다(요청 취소·커넥션 유실)는 뜻이다. 그 경우만 재시도(2회, 총 3시도)하고 HTTP 응답은 그대로 호출부에 넘겨 기존 상태코드 분기(LayoutLoader의 401 재시도 등)와 이중으로 겹치지 않게 했다. 외부signal의 AbortError 는 즉시 rethrow 하고 내부 timeout 이 발화시킨 abort 만 재시도한다.TemplateApp.ts/ComponentRegistry.ts/LayoutLoader.ts—routes.json·components.json·레이아웃 JSON fetch 를 재시도 래퍼로 전환했다. 종전에는 이 요청 중 1건만 취소돼도 재시도 없이 곧장 rethrow 되어 전면 "초기화 실패" / "페이지 로딩 실패" 화면으로 끝났다. 모바일 회선은 요청이 떠 있는 창이 수백 ms 라 새로고침 연타가 그 창에 꽂혀 간헐 재현됐다(로컬은 왕복이 수 ms 라 미재현).LayoutLoader.ts— 실패한 promise 가layoutCache에 남아 이후 모든 재요청이 그 rejection 을 재사용하던 문제를 함께 고쳤다. 네트워크가 복구돼도 다시 시도할 기회가 없었다.TemplateApp.ts— 문서 이탈(pagehide) 중 발생한 초기화 실패에는 에러 화면을 렌더하지 않는다. 새로고침으로 버려질 문서에 "초기화 실패" 를 그려봐야 다음 문서가 덮을 뿐이고, 연타 시 에러가 번쩍이는 원인이 된다. bfcache 복귀(pageshow{persisted:true}) 시 가드를 해제하므로 뒤로가기로 돌아온 문서가 "이탈 중" 으로 오판되지 않는다.visibilitychange/hidden은 의도적으로 쓰지 않는다 — 탭 전환으로도 발화해 백그라운드의 정상적 초기화 실패까지 삼키면 영구 빈 화면이 된다.
확장 번들 부재 시 5초 백지 + 내부 식별자 raw 노출
TemplateApp.ts::waitForHandlers— 확장 JS 번들 로드가 실패로 확정된 경우maxWait(5000ms) 를 기다리지 않고 즉시 반환한다. 종전에는 레이아웃init_actions가 참조하는 확장 소유 핸들러가 영원히 등록되지 않는데도 5초간 폴링했고, 그동안 렌더가 시작되지 않아 사용자에게는 백지로 보였다. 판정은 실패 키 단위로 한다 — 병합 번들(module/plugin) 이 죽으면 그 파일에 속한 확장을 알 수 없으므로 대기를 포기하고, 개별 로딩 실패는{확장식별자}.{핸들러}접두사로 대조해 그 확장의 핸들러만 포기한다. 실패를 특정 핸들러에 귀속시킬 수 없으면 기다린다 — 무관한 확장의 실패로 정상 로드 중인 확장의 기능까지 조용히 사라지면 안 된다.ActionDispatcher.ts— 미등록 핸들러로 인한ActionError에unknownHandler플래그를 부여하고, 표시 계층에서errorHandling정책(토스트 등)을 태우지 않는다. 확장 번들이 없으면Unknown action handler: sirsoft-ecommerce.initPreferredCurrency같은 내부 식별자가 담긴 raw 영문 문구가 사용자에게 토스트로 노출됐다. 확장 부재는 사용자가 조치할 수 있는 일이 아니므로 조용한 기능 열화로 끝낸다.throw자체는 유지해 호출부의 기존 흐름은 바뀌지 않으며, 이 완화는 미등록 핸들러에 한정하고 다른 액션 실패는 종전대로 표시한다.
Changed
확장 에셋 로더의 실패 계약 (resolve → reject)
modules/ModuleAssetLoader.ts— JS 에셋의onerror가resolve()하던 것을reject로 바꿨다. 실패가 성공으로 위장되면 상위loadExtensionAssets의 try/catch 가 무력화되어 실패 사실이 어디에도 기록되지 않고, 앱은 한참 뒤 미등록 핸들러 지점에서 죽는다 — 증상 지점과 원인 지점이 멀어 디버깅이 어려웠다. 실패는 발생 지점에서 표면화한다.- 로드에 최종 실패한 확장을
failedJsAssets에 기록해waitForHandlers가 "오지 않을 핸들러" 를 판별할 수 있게 했다(위 5초 백지 수정의 근거). - CSS(
loadBundleCss/loadCSS)는resolve()를 유지한다 — 스타일 부재는 앱을 죽이지 않는다(과잉 적용 경계). - 개별 확장 로딩(
loadActiveExtensionAssets)은Promise.all→Promise.allSettled로 바꿨다. 확장 하나의 실패가 나머지 확장을 함께 죽이면 안 된다(부분 열화). 반면 병합 번들은 확장 전체가 한 파일이므로 실패 시 reject 로 표면화한다. <script>재시도 시 기존 element 를 제거하고 새로 만든다 — 남겨두면 IIFE 번들이 두 번 실행되어 핸들러가 중복 등록된다.
정상 경로 성능 계약
- 재시도는 실패했을 때만 발동한다.
fetchWithRetry는 성공 시 루프 첫 회에 곧장 반환하므로 백오프 코드가 실행되지 않으며, 추가 비용은 시도별 timeout 용AbortController1개뿐이다(실측: 취소 없는 정상 로드 7회에서 대상 리소스 5종 모두 정확히 회당 1회 요청). - 코어/컴포넌트 번들의
<script src>는 정적 태그로 유지하고onerror에서만 동적 재시도를 건다. 브라우저 프리로드 스캐너는 HTML 파싱 중 정적<script src>를 미리 발견해 선행 로드하는데, 이를createElement('script')로 바꾸면 스캐너가 보지 못해 인라인 스크립트 실행 이후로 요청이 밀린다(실측: 코어 번들 요청 시작 352ms → 568ms, 렌더 중앙값 +273ms). 재시도는 예외 경로이므로 스캐너 이점을 잃어도 무방하다.
사용자 영향: 모바일에서 새로고침을 연타할 때 간헐적으로 뜨던 전면 "초기화 실패" 화면이 사라진다. 요청 1건의 일시적 실패는 재시도로 조용히 복구되어 사용자가 인지하지 못하고, 번들이 끝내 로드되지 않는 경우에도 백지나 내부 식별자 노출 대신 새로고침 버튼이 있는 안내 화면으로 끝난다. 정상적으로 로드되는 경우의 페이지 속도는 종전과 같다.
[engine-v1.52.2] - 2026-07-10
Fixed
다중 progressive 데이터소스의 _localInit 상호 덮어쓰기
template-engine.ts::updateTemplateData()—_localInit을 얕은 스프레드(...data)로 교체하던 것을 소비 여부를 가드로 둔 누적 병합으로 바꿨다._localInit소비부(DynamicRenderer의 useEffect)는 React commit 이후에 실행되는데,initLocal을 가진 progressive 데이터소스가 한 레이아웃에 둘 이상이면 각 소스가 응답 순서대로 독립적으로updateTemplateData({ _localInit })를 호출한다. 두 호출이 같은 commit 사이에 들어오면 나중 payload 가 슬롯을 통째로 교체해, 먼저 도착한 소스의initLocal이 한 번도 관측되지 않은 채 사라졌다._global은 바로 윗줄에서 깊은 병합을 받고 있었으나_localInit에는 그 처리가 없었다.localInitSlot.ts(신규) —_localInit슬롯 병합과__g7LocalInitTracking레지스트리의 단일 소유자. 아직 어떤 렌더러도 관측하지 않은(unconsumed) 슬롯만 누적 병합하고, 이미 관측된 슬롯은 종전대로 교체한다. 그래야 사용자가 화면을 쓰는 도중refetchDataSource가 발생해도 소비가 끝난 과거 payload 가 재적용되어 폼 편집 결과를 되돌리지 않는다._forceLocalInit은 한쪽에만 있으면 보존하고 양쪽에 있으면 최신 타임스탬프를 취한다.DynamicRenderer.tsx—_localInit관측 시 그 슬롯 참조를markLocalInitConsumed()로 기록한다. 해시 계산식을 생산부에 복제하지 않으므로 생산·소비 양쪽의 판정 기준이 어긋날 수 없다. 적용(shouldApply)/건너뜀(동일 해시) 여부와 무관하게 기록한다 — 건너뛴 payload 도 이미_local에 반영되어 있으므로 소비된 것이다. A(setLocalDynamicState)/B(_globalSetState) 이중 저장소 갱신 대칭은 그대로 유지한다.TemplateApp.ts— SPA 레이아웃 전환으로_local을 리셋할 때__g7LocalInitTracking도 함께 초기화한다(resetLocalInitTracking()). 종전에는 리셋 지점이 아예 없어, 새 레이아웃의_localInitpayload 가 이전 레이아웃과 우연히 같으면 "이미 적용됨" 으로 오판되어 건너뛰었다.- 부수 수정 —
init_actions안의refetchDataSource가 React commit 이전에updateTemplateData를 호출하는 경우(pendingDataUpdates큐 경유)에도 blocking 소스의initLocal이 조용히 유실되던 잠재 결함이 함께 닫힌다.
사용자 영향:
initLocal을 가진 데이터소스가 둘 이상인 화면에 SPA 네비게이션으로 진입할 때, 먼저 도착한 데이터소스의 초기값이 간헐적으로 비어 보이던 문제가 해소된다.
[engine-v1.52.1] - 2026-07-10
Fixed
$localized() 가 ActionDispatcher 경로에서 항상 ko 를 반환하던 문제
DataBindingEngine.evaluateExpression()—$localized()의 활성 로케일 해석이context.$locale || 'ko'로 하드코딩 폴백해,$locale을 컨텍스트에 담지 않는 경로(init_actions/actions등 ActionDispatcher 가 빌드하는 data context)에서 로케일과 무관하게 늘 ko 값을 반환했다.$locale부재는 "로케일이 ko" 가 아니라 "이 경로가 로케일을 넘기지 않는다" 는 뜻이다.- 로케일/
templateId해석을$localized와$t앞으로 끌어올려 공유한다.$t가 engine-v1.38.2 에서 이미 갖고 있던window.__templateApp.getConfig()회수 폴백을$localized도 그대로 사용한다. 컨텍스트의$locale이 있으면 그것이 우선하고, 없을 때만 앱 설정에서 회수하며, 그마저 없으면 종전대로 ko 로 폴백한다. - 부수 효과 —
$t의 로케일 회수 조건이!templateId에서!templateId || !locale로 넓어졌다.$templateId는 있는데$locale만 없는 컨텍스트에서 종전에는 ko 로 번역되던 것이 이제 앱 로케일로 번역된다.
사용자 영향: 헤더 배송국가 셀렉터처럼
init_actions파생으로 만든 다국어 목록이, 서버가 내려준 en/ja 이름 대신 한국어로 표시되던 문제가 해소된다.
[engine-v1.52.0] - 2026-07-04
Added
확장 프론트엔드 에셋 서버측 번들 로딩
TemplateApp.loadExtensionAssets()— 활성 모듈/플러그인 에셋을 확장별 개별 로딩에서 서버측 병합 번들 로딩으로 전환한다.window.G7Config.bundleUrls(blade 주입)를 읽어ModuleAssetLoader.loadBundle('module', ...)→loadBundle('plugin', ...)순으로 종류별 1개 번들만 로드한다. 확장 IIFE 자가등록 계약(핸들러/리스너/preblocker)은 priority 순 물리 병합으로 실행 순서가 보존되어 그대로 동작한다.bundleUrls부재 시(구버전 blade) 기존 개별 로딩(loadExtensionAssetsIndividually)으로 폴백한다.ModuleAssetLoader.loadBundle(key, jsUrl?, cssUrl?)(신규) — 병합 번들을 단일<script async=false>+ 단일<link>로 append 하고 key(module/plugin)별 중복 로드를 가드한다.<script async=false>로 번들 내부 물리 순서(=priority 정렬)가 곧 실행 순서다.ModuleAssetLoader.parseBundleUrlsFromConfig()+ExtensionBundleUrls타입(신규) —G7Config.bundleUrls파서. 활성 global 에셋이 없는 타입은 서버가 null 을 내려주어 프론트가 로드를 스킵한다.- 기존
loadActiveExtensionAssets/parse{Module,Plugin}AssetsFromConfig는 폴백 경로로 유지(dead-but-tested).
코어 측 번들 병합 서비스(
ExtensionBundleService)·서빙 엔드포인트·캐시 정책은 루트 CHANGELOG [7.0.2] 및docs/extension/module-assets.md"서버측 번들 병합" 참조.
[engine-v1.51.0] - 2026-07-03
Changed
레이아웃 편집기 번들 분리 (초기 접속 payload 축소)
- 코어 번들(
template-engine.min.js)에서 레이아웃 편집기 코드를 분리해 별도layout-editor.min.js로 빌드한다. 편집기는/admin/layout-editor/*진입 시에만 런타임<script>주입으로 지연 로드된다. 일반 페이지 초기 접속 gzip payload 458KB → 261KB (약 43% 감소). template-engine.ts—LayoutEditorChrome정적 import 제거.renderTemplate()편집기 분기가loadLayoutEditorBundle()(신규)로 번들을 지연 주입한 뒤window.G7Core.__LayoutEditorChrome를 기존과 동일한 provider 트리(Translation/Transition/Responsive/Slot) 안에서 렌더한다. 로드 실패 시 인라인 에러 화면 폴백.checkLayoutEditorMode(dep-free URL 파서)는 코어에 유지.G7CoreGlobals.ts—initCoreRuntimeExports()(신규)가 코어 런타임 표면(DynamicRenderer/엔진 싱글톤/컨텍스트/Logger/AuthManager)을window.G7Core.__runtime으로 노출. 편집기 번들은 빌드 시 alias-shim(layout-editor/__runtime-shims/)으로 이 전역을 빌려 써 코어 런타임을 0바이트 중복으로 재사용하고 싱글톤/React Context 동일성을 보장한다.layout-editor-entry.ts+vite.config.editor.js(신규) — 편집기 번들 엔트리/빌드 설정. React/ReactDOM/react/jsx-runtime은 external →window.React등 단일 인스턴스 재사용(자체 React 사본 미포함). 코어 런타임은 커스텀resolveId플러그인으로 shim 치환.core:build(BuildCoreCommand.php) — 엔진 번들 + 편집기 번들을 순차 빌드.--watch는 양 번들을vite build --watch로 병렬 감시.- 기존 편집기 확장점 stub/큐 핸드셰이크(
initLayoutEditorStub→exposeLayoutEditorGlobals, engine-v1.50.0)가 번들 경계를 넘어 그대로 동작(편집기 로드 시 큐 flush).
DevTools 번들 분리 (디버그 전용 코드 초기 로드 제거)
- 개발자 진단도구(DevTools)의 디버그 전용 무거운 모듈(패널 UI/진단엔진/서버커넥터/스타일추적기)을 메인 코어 번들에서 분리해 별도
devtools.min.js로 빌드한다.initDevToolsAPI()가 디버그 모드(isEnabled())일 때만 런타임<script>주입으로 로드한다. 디버그 꺼진 일반 사용자는 이 코드를 받지 않는다. 레이아웃 편집기 분리와 합쳐 초기 접속 gzip payload 458KB → 221KB (약 52% 감소). G7CoreGlobals.ts—DiagnosticEngine/ServerConnector/StyleTracker/DevToolsPanel정적 import 제거.initDevToolsAPI()는 디버그 OFF 시 최소 API 만 노출(번들 미로드), ON 시loadDevToolsBundle()(신규)로window.G7Core.__devtools를 지연 로드한 뒤 진단/서버덤프/패널을 활성화.G7DevToolsCore(추적 코어, 디버그 무관 항상 필요)는 메인 번들에 상주하며G7Core.__runtime으로 공유.devtools-entry.ts+vite.config.devtools.js(신규) — DevTools 번들 엔트리/빌드. React external →window.React단일 인스턴스,G7DevToolsCore는 shim(devtools/__runtime-shims/)으로 메인 번들과 공유(재번들 0바이트, 싱글톤 동일성).core:build— 엔진 + 편집기 + DevTools 3개 번들 순차 빌드.--watch는 3개vite build --watch병렬.
[engine-v1.50.0] - 2026-06-29
Added
위지윅 레이아웃 편집기 — 인프라 / 진입
/admin/layout-editor/{identifier}URL 분기 신설 —template-engine.tsrenderTemplate()가 코어 컨텍스트 래퍼(Translation/Transition/Responsive/Slot) 안에LayoutEditorChrome셸을 렌더한다.LayoutEditorContext(useReducer 도메인 상태) +EditorToolbar+RouteTreePanel(5그룹: 공통 레이아웃/템플릿/모듈/플러그인/모달,source메타 기반) +EditEntryFab(운영 화면 좌측 하단 편집 진입) 구성.- 진입 경로 — 템플릿 관리 화면에
[코드 편집]+[레이아웃 편집]2버튼, 운영 화면 FAB(편집 권한자 한정,?route={path}로 보던 화면 선택 진입),window.G7Config.activeModules/activePlugins메타 키 신설. - 비활성 템플릿 admin 자산 서빙 —
AdminTemplateAssetController(getEditorAssets/serveComponents/serveRoutes/serveEditorSpec/serveLanguage) +core.templates.layouts.edit가드 + 활성→_bundled폴백. 비활성 템플릿도 편집/저장 가능. - 편집기 셸 다국어 —
$t:layout_editor.*(chrome/palette/insertion/overlay/save/preview/device/zoom/access_error 등) ko/en partial + g7-core-ja 번들 동기. - URL ↔ selectedRoute 양방향 동기화(
buildEditorUrl/useEditorUrlSync, popstate 복원), 좌측 패널 접힘 영속화(localStorage), 라우트 트리 검색 + 키워드<mark>강조(다국어 표시 라벨·path 양쪽 매칭).
프리뷰 캔버스 / 샘플 데이터 / 격리 store
PreviewCanvas— 백엔드 병합 응답(with_source_meta=1)을DynamicRenderer로 실제 렌더하고, 데이터소스는 네트워크 fetch 없이sampleDataProvider(5단계 우선순위: spec_byId→spec_byEndpoint→core_preset→fallback→inferred, 6종 코어 프리셋)로 채운다.__source출처 네임스페이스로 같은 data_source id 의 확장별 shape 충돌을 해소.- 캔버스↔호스트 격리 store façade(
installPreviewCanvasStore) — 마운트 동안window.__templateApp/G7Core.dispatch/modal/toast를 in-memory 격리 인스턴스로 swap, 언마운트 시 복원.ComponentRegistry.createIsolatedInstance()+ActionDispatcher.getHandler()public 노출로 호스트 싱글톤과 충돌 회피. 라우트:param토큰은deriveSampleRouteParams휴리스틱으로 자동 주입. - 출처 메타 옵션 —
LayoutService::getLayout/loadAndMergeLayoutwithSourceMeta로 각 노드에__source:{kind:base|route|extension,...}부여, 편집 응답에만 자식lock_version+__editor.original(저장 SSoT) 동봉. 일반 렌더는 응답 100% 동일.DynamicRenderer가__접두 메타 키를 DOM 전달 직전 일괄 차단. - 반응형 디바이스 미리보기 — 데스크톱/태블릿/모바일 토글 + 줌 슬라이더 +
custom폭 입력(320~1920px 클램프). 디바이스 폭이 캔버스보다 넓으면transform:scale()시각 축소.
요소 추가 / 드래그앤드롭 / undo·redo
- 요소 추가 팔레트(
ComponentPalette) — 카테고리 사이드바 + 검색(영문명·태그·다국어 표시 라벨 매칭) + 그리드 카드.editorSpec.componentPalette.groups(템플릿 소유) 정의대로 카테고리 렌더, 미제공 시 components.json type 폴백.nesting.accepts로 부모가 받을 수 있는 컴포넌트만 노출. 신규 노드 골격은entries[name].defaultNode(템플릿 SSoT, 두 번들 템플릿 전수 작성) 우선·props.default폴백, 미정의 시 클릭 비활성 + 배지. auditeditor-spec-i18n-strings(평문 차단) coverage 등록. - 삽입 어포던스(
InsertionAffordances/useInsertionPoints) — 선택 요소 외곽 4방향 + 버튼, 부모 computed display/flex/grid 로 활성 방향 결정. 잠긴 영역(확장 조각/공통 레이아웃/partial/데이터 반복)에서도 형제 삽입 anchor 기준으로 + 버튼을 노출(잠긴 노드 자체는 무변형). - 드래그앤드롭 재배치(dnd-kit
PointerSensor,pointerWithin) — 명시적 드롭 슬롯 방식(buildDropSlots, slot id 에 컨테이너 path+인덱스 인코딩 → 기하 추론 0). gap/nest 슬롯, 조상 체인 + 형제 컨테이너로 레벨 한정,display:contents래퍼 시각 흐름 투명화,DragOverlay고스트는cloneNode+portal(scale 좌표 왜곡 회피). 컨테이너 밖으로 빼낸 요소를 다른/원래/깊은 컨테이너로 재이동, 선택 기준 드래그(자식 영역에서 시작해도 선택 부모 이동),nestingRules(canDrop/isDraggableNode/isContainerComponent, 폴백 없음). - undo/redo(
useEditorHistory) — 추가/삭제/이동/속성/인라인 텍스트 5종 push, Ctrl+Z / Ctrl+Shift+Z·Ctrl+Y(⌘ 동등), input 포커스 중 미가로채기, 툴바 ↶/↷ 동기. 다른 레이아웃으로 붙여넣기(editorClipboard, sessionStorage 직렬화). - 단축키 보강 + 단축키 맵 모달 — 중앙 키맵 SSoT(
editorShortcuts.ts) + 전역 디스패처(useEditorShortcuts, 입력칸/모달 포커스 가드), 복사·잘라내기·붙여넣기·속성·삭제·부모 선택·선택 해제·요소 추가·코드 편집·미리보기·다국어·저장·나가기·초기화 등 전수 결선.ShortcutHelpModal플랫폼별 표기.
선택 / 잠금 / 속성 편집
- hover/선택 오버레이(
ElementOverlay/useElementSelection) —data-editor-pathDOM↔노드 매핑, hover 점선·선택 실선, 컨텍스트 메뉴(속성/복사/삭제), 8방향 리사이즈 핸들, base/extension 잠금 어포던스, navigate 기반 "이 화면 편집" 네비. 선택 요소 컴포넌트 타입 식별 라벨(작은 요소는 박스 바깥), 잠금 영역 출처 식별 칩 3종(확장명·공통 레이아웃 파일명·데이터소스 id), 겹친 부모 선택(타입 칩 ↑ 버튼 + 키보드 ↑/Esc). - 속성 편집 모달(
PropertyEditorModal) —[설정]/[속성]/[스타일]/[고급]/[동작]/[표시조건]/[번역]탭. 레시피 엔진(recipeEngine, apply 4종: classToken/styleProp/cssVar/propValue, CSS 프레임워크 비가정)이 컨트롤↔노드 패치를 양방향 변환, 위젯 레지스트리(segmented/slider/select/toggle/color/image/tag-input/dimension/icon-picker/options-list 등) +reverseResolve역해석·고급 값 무손실 보존. 편집 중 캔버스 잠금/딤(선택 외 요소 클릭 차단, 모달 스택 파생 자동 해제), 모달 드래그 이동 + 선택 요소 자동 회피. - 코어 제공 "요소 ID" 속성 일괄 부여(
coreProps.ts/CoreIdControl) — 모든 draggable 컴포넌트의 [속성] 탭 최상단에 표준node.props.id편집(바인딩식 읽기전용, HTML 안전 문자 sanitize,coreProps:falseopt-out). 컴포넌트 id passthrough 전수 적용. - 캔버스 모서리/변 드래그 리사이즈(
useResizeHandles, 경량 포인터, scale 보정) + 가로/세로 자유 입력dimension위젯(320px/50%/auto, editor-spec options=프리셋 칩) — 속성 모달과 양방향 동기. - 공유 propControl 21종 + icon-picker/options-list 위젯, 신규 스타일 컨트롤 묶음(글자색·boxShadow·borderStyle/Color/Radius·opacity·overflow·fontItalic·textUnderline·whitespace·justify, editor-spec classToken SSoT), 여백 측별 독립 편집(일괄/개별, pt/pr/pb/pl 공존,
recipeEngine.groupPrefixes+ 다중 토큰 tokenTemplate), 정렬 박스(flex) 노드 파생 판정 + 해제 토글.
색 모드 / 디바이스 분기 / 다크 프리뷰
- 스타일 탭 색 모드(라이트/다크) × 디바이스 직교 2축 편집 — 하나의
StyleScope로 묶어recipeEngine이 baseprops또는responsive.{preset|커스텀범위}.props(다크는dark:classToken)에 무손실 기록·역해석. 다크는 classToken만 편집(인라인 색은 읽기전용 보존), 디바이스 미오버라이드는 base 상속 placeholder, scope별 표시점(●) + "기본값으로 초기화", 디바이스 단일 토큰 편집 시 기본 className 시드로 기본 스타일 유실 방지. 묶음 상단 공용 색모드 탭(ColorSchemeTabs, per-control 복제 금지). 툴바 라이트/다크 프리뷰 토글. - 색 컨트롤 템플릿 스타일 라이브러리 대응 — 프리셋 색은 고정 토큰(
apply.tokens+swatch, 라이트/다크 모두), 자유 HEX는tokenTemplate(text-[{value}]) 라이트 전용(다크 탭 자유입력 비활성 안내). - 다크 프리뷰 색 모드 격리 — 템플릿
darkMode스펙(strategy/ancestorSelector/previewIsolation)으로 코어 CSS 서빙 API 가 다크 조상 셀렉터를 프리뷰 마커(.g7le-preview-dark)로 치환(일반 페이지 CSS 무영향),flattenLayers로 cascade-layer 평탄화,usePreviewDarkIsolation(MutationObserver)로 어드민html.dark침범 차단, 권한 가드 CSS 는 Bearer fetch→<style>주입.
디바이스별 별도 구성(반응형 자식 교체)
responsive.{키}.children자식 완전 교체형 편집 정합 — 모바일/태블릿 보기에서 보이는 구성 요소가 정확히 선택·수정되고(같은 자리 PC 구성 오선택 차단), 분기 출처 배지(모바일 구성), 구성 경계 이동 차단, ComponentPath 가 디바이스 분기를 1급 표현(responsive.{키}.children.{N}). 디바이스 전용 구성 분리/해제(기본 구성 복제 시작, 포함 관계 안내), 디바이스 목록 동적 수집(기본 4종 + portable + 사용자 폭 구간 다수), 다른 디바이스 구성 이동 버튼·전용 구성 안내 배지, portable 구간 스타일 표시, 더 좁은 디바이스 전용 분리.
다국어 인라인 편집 / 데이터 칩 / 커스텀 키 관리
- 콘텐츠 로케일 전환(
LocaleSwitcher, 캔버스 프리뷰만) + 더블클릭 인라인 편집(useInlineEdit/InlineTextEditor, 평문·기존$t:키 모두$t:custom.*자동 생성) + 인라인 서식 툴바(InlineTextToolbar, styleControls 선언분만) + 속성 모달 [번역] 탭(TranslationField, 전체 로케일 일괄 PUT). 낙관적 즉시 반영(키 생성 왕복 깜빡임 제거) + "모든 언어 편집" 바로가기. - 텍스트(보간) 데이터 연결 — 평문+다국어+데이터 공존: 평문 부분 다국어 키화(param 정규화, 언어별 어순 보존), 데이터는 드래그 가능한 원자 칩(글자 사이 어디로든 이동, 언어별 독립 위치, 캐럿 미리보기), 인라인
+데이터(커서 위치 삽입), 삽입=즉시 키화([번역] 탭 활성), 칩 X 로 전 로케일 해제. 대상 컴포넌트 12종 자동 인식 + capabilitytextBindingopt-in/out, [번역] 탭 자리표시 보호 가드. - 데이터 칩 전면 확대 — 속성 창 텍스트 칸·선택지 라벨·목록 항목·표 셀·"다국어 키 관리" 화면·컴포넌트 속성 전반·페이지 설정 전 탭의 값 입력칸에서 데이터 칩/표현식을 동일하게 다룬다(값 전용 칸은 번역키 미생성). 칩 파서가 논리식(
||)·파이프 서식(| date/| 숫자)·중괄호 객체 처리·다중 데이터를 정확 분리, 칩 이름은 핵심 데이터명만 표시. 설정참조($core_settings:/$module_settings:/$plugin_settings:)도 친화 칩으로 시각화(공용inlineBindingUtilsSSoT). - 커스텀 다국어 키 관리 모달(
CustomTranslationManager) — 목록·필터(전체/사용중/미사용)·로케일별 일괄 편집(낙관락 PUT, 409 안내)·삭제. 좀비(고아) 키 자동 표시 — 저장 시점 백엔드 리스너(MarkOrphanedCustomTranslations+CustomTranslationUsageScanner)가 미참조 키를orphaned전이(런타임 병합 자동 제외), 캔버스 실시간 사용 여부 라이브 배지 병행. - 텍스트 propControl 동적 다국어 인프라 —
widget:"i18n-text"+apply:propValue컨트롤을I18nTextField(미리보기 + ko/en/ja 펼침 + 바인딩 읽기전용)로 전수 자동 승격. 공통 SSoT(useCustomTranslationcommitText/classifyCustomText)로 인라인·목록·propControl·label_key 통일. children 항목 텍스트도 동일 위젯 승격(공용nodeTextPath).
데이터 바인딩 / 데이터 소스 편집기
- 데이터형 prop ↔ 데이터소스/상태 바인딩 — capability
dataProps(shape scalar/array/object) 선언분만 [속성] 탭 "데이터 연결" 영역 노출(구조/수치/enum prop 비대상). 🔍 검색형 데이터 피커(친화 명칭·소스·경로·미리보기값 매칭, shape 일치 후보만) + 순수 후보 빌더(bindingCandidates, 편집기 샘플 데이터·_global·per-state local/query/route·_computed평탄화) + 상태값 명칭 카탈로그(stateLabels,$t:키, 로케일 동적 대응).parseBindingExpression이 안전 바인딩(?./?? [])을 정규화 인식,buildBindingExpression이 shape별 안전 형태로 재기입. 양 템플릿 전 컴포넌트 dataProps 전수 선언, 입력/선택 계열 19종 보강, auditeditor-datapropspec-shape-valid. - 반복(iteration) 데이터 연결 공용 영역(
IterationBindingSection) —node.iteration.source를 가진 모든 노드의 [속성] 탭 최상단에 "반복 데이터 연결"(array shape 후보, item_var/index_var 읽기전용 힌트). - 데이터 소스 편집기(
DataSourcesPanel, 페이지 설정 [데이터] 탭) — 현재 레이아웃 data_sources CRUD(id/label_key/type/endpoint/method/auth/loading/params/fallback, JSON 검증). 자체/상속 분리 저장(patchDocumentRaw,__editor.originalSSoT), 신규 소스 즉시 검색 후보 반영. 양 번들 템플릿 134 data_source + 확장 주입 118 data_source 전수label_key부여 + 확장 출처 배지(모듈/플러그인), auditdata-source-label-key-coverage.
노드 에디터(목록 / 표 / 배열)
- 범용 노드 에디터 슬롯(kind-agnostic) — capability
nodeEditor:{kind,params}/canvasOverlay:{kind,params}+ 레지스트리(nodeEditorRegistry/canvasOverlayRegistry) +registerCoreEditors. 코어는 kind 만 알고 디스패치(분기 0, 템플릿 재등록 가능), 미등록 안전 디그레이드. 템플릿 확장점G7Core.layoutEditor(registerWidget/registerNodeEditor/registerCanvasOverlay) + ready 큐(initLayoutEditorStub, initTemplate 선등록 유실 방지). - 목록 children 노드 에디터(
ChildrenListControl) — Ul/Ol/Nav/Form/Li 자식 트리 추가/삭제/정렬, 항목 텍스트 다국어(직접 text + 중첩 텍스트 자손 탐색, 장식 노드 보존), [속성] 탭 배치(스타일 탭=CSS 전용). 폼 항목 라벨+입력칸 묶음 추가·itemFields/childTemplate/childLabel스펙 선언. - 표 노드 에디터(
TableEditor) + 캔버스 인플레이스 오버레이(TableInplaceOverlay) — 트리↔논리 grid 어댑터(tableGridModel, 병합 고려), 행/열 추가·삭제·이동(밴드=병합 블록 단위, 섹션 경계 가드, 이동 불가 시 비활성+사유), 셀 병합/해제, 2단계 선택(표→셀)+Shift 영역 선택, 셀 테두리/배경색/내부 여백 시각 피커(프리셋 토큰+자유 HEX 인라인 SSoT, 라이트/다크 공용 탭, border-collapse 공유 변 보정), 셀 텍스트 다국어(복합 셀 구조 보존)+인라인 서식 툴바, 전용 거터 레일+빈 셀 최소 크기(편집기 전용 CSS), 속성 패널 미니 미리보기 실테두리 반영. - 배열 노드 에디터 —
ArrayItemsEditor(정적 배열 prop 항목 추가/삭제/정렬/필드편집, 위젯 text/i18n-text/select/boolean/icon/number/color/number-list, 원시string[]지원, 정적-바인딩 가드,defaultItems시드)·array-group(다중 배열 prop, BarChart labels+datasets)·array-cell-tree(중첩 cellChildren, CardGrid cardColumns). 템플릿 인플레이스 레퍼런스(registerCanvasOverlay,data-editor-item-path마커). TagInput 계열 5종 options 편집기 보완.
표시조건 / 동작 / 페이지 상태
- 표시조건 편집(
ConditionBuilder/conditionRecipeEngine) — 친화 조건 카테고리(로그인/권한/데이터 유무·로딩·실패/필드 값/화면 상태/수정·생성/입력 오류) 택1 + "그리고/또는" 결합(단일 표현식 합성). 변종 방어 인프라 4계층(recipe-local alias·specificity 우선 매칭·path-shape 가드+backreference·전역 정규화 위임), auditcondition-recipe-duplicate. - 동작(액션) 편집(
ActionRecipeEditor/actionRecipeEngine) — 이벤트별 친화 명칭(이동/메시지/상태 바꾸기/새로고침/서버 호출), apiCall onSuccess/onError 중첩 재귀 조립, 올바른 핸들러 규칙(navigate/apiCall/setState/toast/refetchDataSource, top-level target) 생성. 동작 이벤트 친화 명칭 15종 보강(auditeditor-event-label-coverage). - 동적 핸들러 이름 디스패치 — 액션
handler가{{...}}바인딩이면ActionDispatcher.executeAction이 컨텍스트로 먼저 해석한 뒤 라우팅한다(resolveActionRef직후, 프리뷰 억제·switch 앞). 백엔드 응답이 호출할 핸들러 풀네임을 내려주는 provider-agnostic 디스패치(결제 진입handler: "{{response.data.pg_payment_handler}}")를 지원. 빌트인 26종은 리터럴이라 미진입(무영향), nested(conditions/sequence)도 동일 경유로 자동 적용, 프리뷰 억제 판정·미등록 graceful skip 도 해석 이름 기준. 편집기 "결제 진입" recipe(requestPgPayment)로 친화 입력(핸들러·결제 데이터 칩,chipContext='response'),actionRecipeEngine.matchAction핸들러 비교를 placeholder-aware 로 보강(리터럴은 정확 일치,{{key}}는extractValues위임). - 중첩 액션 친화 편집 확대 — apiCall
onSuccess/onError와sequence/parallel의 actions 를advanced잠금에서 풀어 친화 중첩 액션 빌더(action-list)로 편집한다(응답 후속·다단 동작을 코드 없이 추가/순서/속성·데이터 칩 편집). 재귀ActionListBuilder가 동일 recipes·candidatePools 로 펼쳐지며summarizeAction은 action-list 트리 param 을 카드 요약에서 제외([N]토큰 누출 방지).switch.cases는 객체맵 구조라 잠금 유지. conditions핸들러 친화 recipe +branch-list위젯 신설 — 조건 분기([{if, then}])를 분기별 실행조건(조건식 데이터 칩)·동작(중첩 액션 빌더, 단일/배열 both) + 분기 추가/삭제/순서로 친화 편집. recipe build 는conditions를 액션 최상위 키({handler:'conditions', conditions:'{{branches}}'})로 두어handleConditions와 정합, sole-binding 통째 캡처로then구조 무손실 왕복. 이 셋이 합쳐져sequence → apiCall.onSuccess → conditions → then깊이의 결제 진입까지 코드 없이 편집 가능(특정 템플릿/모듈 비의존, 코어 전역).- 동작 입력칸 객체값·경로형 setState 표시 정합 — (1) apiCall
body위젯을data-chip(스칼라/표현식)에서key-value로 바꿔 필드 맵({temp_order_id, orderer, ...})을 키별 데이터 칩 행으로 편집한다(객체를 단일 칩으로 받아[object Object]·JSON 분해 깨짐이 생기던 문제 제거). (2) setState 의 경로형({target:'_local.X', value:V})을 인식해value를 상태 키 이름이 아니라 그 경로에 넣을 단일 값으로 표시(타입 보존InitialStateValueEditor). (3) 컴포넌트 [동작] 탭이bindingCandidates를 동작 입력칸까지 전달하지 못해 데이터 칩 추가(🔍) 가 안 뜨던 회귀를 수정(PropertyEditorModal→ActionRecipeEditor). - 페이지 상태 토글 + 시뮬레이션(
PageStateSwitcher/pageStateSimulator) —editor-spec.states의 sampleData 오버라이드·초기_local/_global/query/route패치·폼 검증 실패를 캔버스에 재시뮬레이션(_localInit권위 주입으로 init_actions 이김). scope 매칭(route glob/base/modal, 실제 path 일치), 번들 템플릿 states.json 전수 작성(profile/edit·settings·users 등). 반복 항목 편집 모드 진입 골격.
버전 히스토리 / 실데이터 미리보기 / 확장·모달 조각 편집
- 버전 히스토리 모달(
VersionHistoryModal/useLayoutVersions) — 저장 버전 목록(번호·시각·저장자·변경량 +N/-N/char_diff·최신 배지) 조회·복원(reload캐시 버스트+activeDocument 재로드, 낙관적 잠금 정합) + 버전 비교 diff(VersionDiffView/lineDiff, 자체 LCS·Unified diff·외부 라이브러리 0). 실데이터 미리보기(useLayoutPreview) — 미저장 문서를 저장 마스킹 후storePreview(30분 TTL)→/preview/{token}새 창,mod+p결선. - 확장(주입 조각) 시각 편집 — 호스트 레이아웃 전체를 백엔드 평가 상태 그대로 렌더 + 편집 조각만 역스포트라이트, 모달 안 주입 조각(약관/주소 검색/본인인증)도 모달 열어 편집, 화면 상태 전수 정의(배송지 직접 입력·모바일 드로어·쿠키 배너·이니시스 등), 시각 편집 불가 시 코드 편집 안내 디그레이드. 라우트↔모달/확장 연결 목록(정적 매칭
host_layouts, 트리 인라인). 확장 버전 기록·복원·트리 버전 배지(useExtensionDocument/VersionTarget일반화 + 백엔드 LayoutExtensionVersion*). - 공통 레이아웃 슬롯 가시화+잠금(점선 "콘텐츠 영역" 라벨, 선택/드롭 전체 잠금), 헤더/푸터 선택 가능(시각적 루트 editorAttrs 전달 + 핸들 클릭 최심 노드 위임).
저장 / 동시성 / 접근 오류 / devtools
- 문서 저장·dirty·동시성(
useLayoutDocument) —patchLayout/save(expected_lock_versionPUT, 활성 확장 재검증, 200/409/422/network 분기), 저장 마스킹(stripInheritedFromLayoutContent, base/extension/partial+메타 제거), 라우트 전환 세션 캐시 복원 + 트리 dirty 배지(●) + beforeunload 가드, 레이아웃 초기화(reload).SaveFeedbackBanner(6종 SaveResult, concurrent 모달). 낙관적 잠금 인프라(lock_version컬럼 +ConcurrentModificationException+ 409 + 백엔드UpdateLayoutContentRequest::prepareForValidation마스킹 가드). - 접근 오류 패널(
AccessErrorPanel) — kind별(unauthorized/forbidden/not_found/server_error/network/unknown) 아이콘·톤·필요 권한 칩·액션, 401 자동 로그인 redirect(AuthManager.getLoginRedirectUrl), 자산/라우트 실패 통합.access_error.*키 17건. - devtools 트래커 다수 — editor-state/selection/dnd/document/history/sample-data/spec-merge/property-patch/i18n/page-state(메타 키만 적재, 노드 내용물·평문 미적재, 언마운트 clear).
- 공용 모달 인프라(
EditorModalContext/EditorModalRoot/useEditorModal) — 코어_global.modal과 분리(격리 store 충돌 회피), 백드롭/ESC 닫기, depth 무제한 스택. 부유 드롭다운 공용화(FloatingDropdown,position:fixed+anchor rect 로 flip/clamp 자동 보정, 외부 pointerdown/ESC 닫기) — 데이터 검색 피커·검색 드롭다운 전반 적용.
페이지 설정 모달(8탭)
- 페이지 설정 모달 — 기본 정보·검색엔진·화면 동작·로딩 화면·자동 계산·초기 상태·에러 처리·데이터 8탭, 탭별 고급 항목 배지, 즉시 반영(영속은 툴바 저장). 켬/끔 하위 설정은 숨기지 않고 비활성(회색) 표시, 공용 토글 스위치, 모달 최대 높이 화면 적응 + 내부 스크롤, ESC 닫힘 차단([닫기]/[✕]만).
- 기본 정보 — 페이지 이름·설명·편집기 트리 라벨(다국어+데이터 칩), 메뉴 아이콘 고르개, 접근 권한 태그.
- 검색엔진(SEO) — 노출 켬/끔·페이지 종류·연동 확장/데이터·sitemap 우선순위/주기·검색 제목/설명, 소셜 공유(OG/Twitter, 비운 칸 기본값 출처 배지+잠금, 고급 이미지 옵션), 구조화 데이터(직접 지정 토글, 확장 자동값→연결 칩 출발, JSON-LD 미리보기), 검색 변수(자동/값채움/직접 추가 3그룹), 봇 미리보기(서버 권위 계산). 확장 제공 SEO 자동값을 출처 연결 칩 + [다른 데이터로 바꾸기]로 표시(언어팩 키 기반 명칭).
- 화면 동작 — 핸들러 카탈로그(코어+확장)에서 골라 순서 배치(드래그 손잡이), 친화 요약+코드 보기+출처 배지+실행 조건, 부모 상속 동작 잠금(읽기 전용). 모든 동작 입력칸 데이터 칩·표현식 친화 입력(저장소 키·상태 값·setState 키–값·navigate query 등), 모달 선택
modal-picker위젯(친화 명칭). - 로딩 화면 — 켬/끔·덮을 범위(전체/특정 영역, 영역 고르개)·표시 방식 5종+옵션·기다릴 데이터, 상속 표시 + [이 화면만 바꾸기], 안내 문구 다국어, 별도 컴포넌트 선택 창.
- 자동 계산 — 친화 보기 + "직접 만들기" 3단계 + 샘플 데이터 결과/타입 미리보기, 부모 계산값 덮어쓰기/되돌리기, 모든 값·경로 칸 데이터 칩·표현식, firstOf 후보·배열 인덱스 평문 유지.
- 초기 상태 — 로컬/전역/격리 시작값(문자/숫자/예아니오/없음/목록/묶음+중첩 블럭 편집), 종류 선택 추가, [코드로]/[블럭으로] JSON 전환(문법 오류 저장 차단), 값 이름 검증(영문 시작), 부모 상속 표시·덮어쓰기.
- 에러 처리 — 상태 코드별 동작 행(표준 401~503 기본 표시, 출처: 이 페이지/상속/템플릿), 7종 동작+오류 정보 데이터 칩, 코드 미리보기, 상속 덮어쓰기 안내.
- 데이터 탭 — 데이터 소스 편집을 종류별 섹션(본문 타입·성공/실패 후속·코드별 오류·조건부 로딩·재진입 재요청·실시간 수신)으로 확장 + 전역 헤더·외부 스크립트 읽기전용.
표현식 분해 트리
- 조건에 따라 달라지는 제목·설명·값을 조각별 분해 편집 — 조건 분기(
A ? B : C)·기본값 폴백(A ?? B/A || B)·이어붙이기(A + B)를 트리로 풀어 각 분기를 다국어 입력칸(번역 탭·데이터 칩)으로 편집, 중첩 재귀 표현. 한 줄 미리보기+[수정]/[접기], [원본 식 보기] 토글, 친화 빌더 가능한 단순 조건만(복잡 조건 읽기전용으로 원본 무손상). 조각 손잡이 드래그 순서 변경·추가([+조각]/[+값이 없을 때 대신])·삭제, 일반 이름↔표현식 양방향([표현식으로 바꾸기]/[일반 이름으로], 되돌림 미리보기·경고, 첫 결과 데이터 칩 복구). 페이지 이름·설명에서 검색엔진 값 칸·컴포넌트 속성 전반으로 확대.
본인인증(IDV) 인증 대상 선언
apiCall액션에identity_target선언 속성 신설 — IDV 428 인터셉트 시 인증 코드/링크를 보낼 대상(이메일·전화)을 흐름이 직접 선언한다.auth_mode/errorHandling과 동일하게 apiCall 액션 최상위에 두며,{ email?, phone? }형태로 표현식 바인딩을 지원한다.ActionDispatcher가 428 인터셉트 지점에서 이 값을 평가해IdentityGuardInterceptor.handle(response, originalRequest, target)로 전달하고, launcher 가verification.target한 곳에서 읽는다. 비로그인(게스트) 흐름의 핵심 — 서버 428 payload 에는 target 이 없고(서버는 화면 입력값을 모름) 흐름이 선언한다. 로그인 사용자는 빈 값이어도 서버 세션이 도출하므로 무방하다. (배경: 비회원 주문 결제 시 본인인증 정책이 켜지면, 주문자·수취인에 이메일·전화를 모두 기재해도 launcher 가 주문자 입력값을 읽지 못해 "인증 대상이 필요합니다" 422 로 막히던 결함. 기존 launcher 는 회원가입/비밀번호 재설정 폼 경로만 하드코딩으로 알았다.)G7Core.api(axios) 호출 경로도config.identity_target로 동일하게 IDV 대상을 선언할 수 있다 —apiCall(fetch) 경로와 axios 경로 양쪽 모두에서 인증 대상 전달을 지원한다.ensureIdentityVerified액션이params.target으로 선제 가드 시점의 인증 대상을 선언할 수 있다 (apiCallidentity_target과 동일 채널).- 레이아웃 편집기 apiCall 레시피에
identity_targetemail/phone 입력칸 추가([고급] 영역) — 코드 편집 없이 인증 대상 선언을 편집할 수 있다.
레이아웃 편집기 SEO 다국어 데이터 칩
- 레이아웃 편집기 검색엔진 탭(페이지 설정)의 데이터 칩 파서가 SEO 다국어 추출 함수
$localized(<경로>)를 단일 바인딩으로 인식하도록 확장. 종전엔$localized(product.data.meta_title)같은 함수 호출이bindingCandidates.parseBindingExpression·expressionValueTree양쪽에서 복합식(raw)으로 떨어져, SEO 메타값을 입력해도 편집기가 친화 데이터 칩 대신 원시 식 문자열을 노출했다. 이제parseBindingExpression이$localized(<단순경로>)래핑(인자 1개·단순 경로)을 흡수해 인자 경로를 단일 바인딩으로 인지하고localeFn으로 함수명을 보존하며(bindingChipLabel이 인자 경로를 친화 라벨로 표시),expressionValueTree파서가 같은 형태를 단일 바인딩 리프({{$localized(path)}})로 환원해meta 우선 ?? name 폴백체인도 fallback 트리로 분해한다.buildBindingExpression(sourceId, path, shape, localeFn)에 옵션 인자를 더해 데이터 교체 시에도$localized(<src>.<path>)(옵셔널 체이닝/폴백 없음) 래핑을 보존한다(래핑이 빠지면 다국어 객체가 현재 로케일 문자열로 추출되지 않아 SEO 메타가 깨짐). 다인자·연산·리터럴 인자, 미등록 함수(Math.max등)는 종전대로 복합식(raw) 폴백이라 손상 0. (배경: 상품·카테고리 SEO 제목/설명 다국어화 후, 사용자가 검색엔진 탭에서 설정한 SEO 메타 항목이 편집기 데이터 칩으로 표시되지 않던 결함.)
Changed
- 동작 순서 변경 방식을 표현식 편집기와 통일 — 컴포넌트 [동작] 탭·페이지 설정 [화면 동작] 탭·데이터/에러 처리 동작 목록에서 손잡이 드래그 + 파란 삽입선으로 재배치(▲▼ 버튼 제거, 공통 레이아웃 상속 동작은 잠금).
- 코어 JSON 텍스트 편집 입력기를 공용 부품(
JsonBlockField)으로 통일(데이터 소스 요청 파라미터·확장 주입 속성·초기 상태 [코드로] 동일 동작/오류 안내), 데이터 소스 [기본값]을 중첩 블럭 편집으로 일원화(raw JSON 모드 제거), [요청 파라미터]·[기본값] JSON 미리보기를 [미리보기 ▾] 토글로 전환. - 데이터 검색 선택기를 모든 진입점에서 부유(
FloatingDropdown) 상태로 통일(자동 위치 보정), 로딩 화면 기다릴 데이터·검색엔진 연동 데이터 목록을 [데이터] 탭과 동일 표기(표시 명칭+id+확장 출처 배지)로 직관화. - 페이지 설정 항목 이름을 편집 중인 화면 단어장 우선 해석(관리자 단어장 폴백)으로 정정,
errors/{code}layout_name 식별자 통일(TemplateManager자동 부착 제거),ActionDispatcher.handleOpenWindow/navigate fallback 기본_self(교차 이동 의도치 않은 새 탭 차단),recipeEngine.applyRecipe/reverseResolveoptional scope 인자(기본 BASE_SCOPE 동일 동작), 편집 모드 nesting 컴포넌트editorAttrs패스스루(layout/composite 시각적 루트 spread, 사용자 페이지 no-op). editorSpecLoader가 편집 대상 + 활성 모듈/플러그인 editor-spec 을 단일 병합본으로 합침(record key 병합·palette/states concat·nesting union·sampleData key 병합),sampleGlobal코어 우선 deep merge 체인(충돌 시 코어 값+dev 경고), 라우트 트리 노드에 레이아웃 파일 경로 표시·툴바 템플릿 이름/버전+전환 드롭다운.IdentityGuardInterceptor.handle()시그니처에 3번째 인자target?: { email?, phone? }추가(옵셔널, 하위호환).VerificationPayload에 런타임 필드target추가,IdentityVerificationTarget타입 export.
Fixed
- 페이지 설정 [화면 동작] 탭이 기존 동작을 못 읽어 추가·저장 시 기존 동작이 통째로 사라지던 데이터 손실, "설정 저장하기" 저장할 값이 번역 문구용 읽기전용으로 잘못 배정, "화면 상태 바꾸기" 깊은 중첩 묶음이
[object Object]로만 표시·편집 불가, 실행 조건(if) 칸이 평문이라 데이터·표현식 미연결, ≡/🔍/ƒx/?? 버튼 겹침·행 어긋남 등 동작 입력 결함. - 평문+데이터 칩 혼합 값을 [표현식으로 바꾸기] 승격 시 중괄호 중첩(
{{...'{{route.id}}'...}})으로 식이 깨지던 문제 — 데이터 칩을 이어붙이기 식 데이터 항('...' + route.id)으로 보존. [일반 이름으로] 되돌리기 시 첫 결과 데이터 연결이 빈 칸으로 사라지던 문제, 단일 데이터에 불필요한 표현식 편집기 펼침, 검색엔진 데이터 칸 [✓ 완료] 버튼 부재, [✎ 수정] 모드에서 데이터·설정 참조 raw 노출(칩 편집기 유지로 정정). - 자동 계산 "먼저 있는 값 고르기" 후보 소실·깨진 식 저장·데이터 선택 칸 부재, 검색엔진 SEO 동적 변수 그룹 미분류(확장 정보 미전송), 미리보기 샘플 데이터 미반영·출처 표시 미갱신·상속 값 오표기, 로딩 화면 상속 표시·영역 고르개·구조화 데이터 자동 채움, 에러 처리 표준 상태 코드 행 누락·두 번째 입력칸 입력 불가·타이핑 소실(번들 errorRecipes.json setState 스프레드/openModal target/showErrorPage target 정정 포함), 모달 화면 높이 초과·탭 색 줄 잔존 등 페이지 설정 다수.
- 데이터가 든 다국어 문구 인라인 편집 시 계산식 일부(
|| []).length}}등)가 평문 노출·칩 이름 깨짐(관리자 사용자 관리·대시보드·본인인증·언어팩 등 개수/페이지 정보 화면 전반), 객체 형태 계산식·{{error.errors ?? {}}}빈 객체 fallback 미평가, 모달 화면명 키 노출. - 표 셀·목록 항목에 데이터 칩 끼운 직후 "데이터 영역" 잠금으로 인라인 편집 차단·표 미리보기 격자 키 문자열 노출, 데이터 칩 글자 사이 드래그 시 trailing click 으로 해제·
{p0}자리표시 잔존. - 요소 이동 후 undo 시 복제(중복) 발생, 디바이스별 구성 안 요소 추가·이동·undo 미반영, portable 구간 라벨 "모바일" 오표기·스타일 미표시·전용 분리 불가·분리 해제 시 스타일 동반 삭제.
- 인라인 편집 중 글자색 classToken 실시간 미반영(폴백 인라인 color 가 토큰 색 가림 — 색 출처 없을 때만 적용), apiCall onSuccess 중첩 동작 추가 불가, 동작 카드 드래그 핸들 무동작, 영역 선택(🎯) 모드에서 편집 표식 미숨김·임시 ID 요소 선택 가능·딤 막 클릭 가로채기·고른 요소 잔류 선택.
- flex 노드 파생 판정 stale(DOM computed 고정), 캔버스 잠금 딤이 닫은 뒤 잔존(모달 스택 파생으로 자동 해제), 표시 권한 TagInput 후보 미주입(+추가 영구 비활성), 데이터 바인딩 요소 선택·이동 불가·네비 어포던스 오해 안내, 화살표 함수
arguments부재로 인한 data_source 3번째 인자 판정 결함(단위 테스트 검출). - 서버 응답 캐시 키와 무효화 경로 정규화 불일치로 저장 후 사용자 간 stale 노출(serve
?v정수화, 일반/편집기.meta키 동시 무효화), 편집기 후보 데이터를 admin 전역 broadcast 가 아닌 가드 엔드포인트 fetch 로 한정. replace:truenavigate(탭 전환·필터 변경 등 같은 화면 내 URL 교체) 경로에서 데이터소스if조건이 재평가되지 않던 버그 수정.updateQueryParams가 직전 진입 시점에if로 필터링된currentDataSources스냅샷을 그대로 refetch 하여, 탭을 클릭으로 전환하면 변경된query컨텍스트가 반영되지 않고 직전 탭의 데이터소스가 계속 선택되었다. 이제 원본(if필터링 전) 데이터소스를 보존(currentRawDataSources)하고,updateQueryParams에서 변경된query+ 최신_global로filterByCondition을 재평가하여 현재 탭에 맞는 데이터소스만 fetch 한다. (새로고침/URL 직접 진입 경로는 기존에도 정상 — 본 수정은 SPA 탭 클릭 전환 경로 전용.)- iteration source / if 조건 / 반복 텍스트 바인딩에서 배열·객체 리터럴(
{{['a','b']}},{{[{...}]}})이 렌더되지 않던 버그 수정.isComplexExpression(RenderHelpers.ts) 정규식이[/]/{/}를 인식하지 못해 리터럴이 "단순 경로"로 오판되어resolve()경로탐색 →undefined가 되었다. 정규식에[]{}를 추가하여 리터럴이evaluateExpression(원본 타입 유지) 경로로 라우팅된다. 순수 숫자 대괄호 인덱싱(items[0],entry[1])은resolve()/evaluateExpression()양 경로 결과가 동일함을 회귀 테스트로 입증 — 경로 이동에 따른 동작 변화 없음. (배경: 본인인증 이력 화면의 상태/발생위치 필터 체크박스가 배열 리터럴 source 로 정의되어 전혀 렌더되지 않던 결함.) evaluateExpression(DataBindingEngine.ts) 에서new Set(...)/new Map(...)가Set is not a constructor로 실패하던 버그 수정.extractVariablesFromExpression의reserved화이트리스트에Set/Map(및WeakSet/WeakMap/Symbol/Promise/BigInt/Error)이 없어 이들이 컨텍스트 변수로 오인되어undefined로 가려졌고,new Function본문에서new undefined()가 되었다.Math/Date/Array등과 동일하게 표준 내장 객체로 화이트리스트에 추가한다.eval/Function/globalThis/window등 위험 전역은 의도적으로 제외. (배경: 본인인증 이력 화면의 채널 필터가Array.from(new Set(...))로 후보를 도출하던 결함.)ApiClient(G7Core.api) response 인터셉터에 HTTP 428 본인인증(IDV) 중앙 처리 추가. 기존에는apiCall핸들러(ActionDispatcher.handleApiCall, native fetch) 경로만IdentityGuardInterceptor로 428 을 자동 인터셉트했고,G7Core.api(axios) 직접 호출 경로(모듈 JS 핸들러: 무통장 입금확인·주문 취소 등)는 401 만 처리해 428 이 와도 본인인증 모달이 뜨지 않았다. 그래서 각 모듈 핸들러가G7Core.identity.handle분기를 수동으로 복제해야 했고, 누락 시 본인인증 가드가 무력화됐다(주문 취소 핸들러 미노출 회귀). 이제 axios 인터셉터가428+identity_verification_required응답을 감지해 본인인증 모달 → verify → 원 요청(헤더/body 재사용) 자동 재실행을 중앙에서 수행한다.apiCall(fetch) 경로와는 별개 HTTP 클라이언트라 이중 처리되지 않는다.createChangeEvent(EventHelpers.ts) 가 checkbox/radio 이벤트의target.value를String(checked)("true"/"false") 로 문자열화하던 버그 수정. Toggle/Checkbox 가 명시적value없이createChangeEvent({checked})로 만든 이벤트가 Form 자동바인딩의 value 바인딩 경로(currentValue 가 boolean 이 아닐 때 — 예: 폼 초기값이 아직 채워지지 않은 시점)로 처리되면, boolean 필드에 문자열"true"가 저장되어 백엔드 boolean 검증이 422 로 거부되었다(예: 이커머스 환경설정 "취소 시 재고 복구" 토글 ON 저장 실패). 이제 checkbox/radio 의 기본value를 boolean(checked) 으로 두어, checked 바인딩 경로(target.checked)는 기존과 동일하게 동작하고 value 바인딩 경로도 boolean 을 저장한다. 명시적value를 받는 호출(PermissionTree, HtmlEditor 의 textarea/multilingual)과 checked 만 읽는 호출(HtmlEditor isHtmlMode)은 영향 없음. (배경: troubleshooting-components-form.md 사례 5 — "엔진이 boolean 을 올바르게 처리, 문자열 변환 우회 불필요" 설계 의도와 정합.)- 컴포넌트
setState(target: "_local")가 canonical source(_global._local)에 동기화되지 않아, 검색(navigate replace:true) 직후 사용자가 선택한 필터가 풀리던 버그 수정. 목록 화면에서 필터(라디오/체크박스 등)를 선택하면ActionDispatcher.handleSetState의 COMPONENT path 가 컴포넌트 React 상태(저장소 A:localDynamicState)만 갱신하고_global._local(저장소 B)은 갱신하지 않았다. 이후 검색 →updateQueryParamsrefetch →updateTemplateData가currentDataContext._local을 stale 한_global._local(init 기본값)로 되돌려, 선택한 필터 일부가 검색 직후 초기값으로 풀렸다(새로고침은handleRouteChange가query기반으로_global._local을 재구성하므로 정상). Form 자동바인딩은 이미setLocal(render:false)로 양쪽 저장소를 동기화하지만 명시적setState target:"_local"은 그렇지 않던 비대칭이 원인. 이제 COMPONENT path 도 GLOBAL STATE UPDATER path 와 동일하게globalStateUpdater({ _local }, { render: false })로_global._local을 동기화한다.render: false이므로 추가 React 렌더는 발생하지 않으며(클릭당 렌더 횟수 불변),scope: 'parent'|'root'및 isolated 타깃은 이 분기에 도달하기 전에 분리 처리되어 모달 상태 오염에 영향이 없다. (배경: 쿠폰·주문·배송정책 등 목록 화면 공통 결함.) emitEvent액션의 결과(_local._eventResult)가 같은 sequence 의 후속 액션에 전파되지 않던 버그 수정.handleSequence는 비-setState 핸들러 실행 후currentState를__g7SequenceLocalSync(setLocal 이 설정)에서만 갱신하는데,emitEvent는globalStateUpdater로만_local을 갱신해__g7SequenceLocalSync를 비워둔 채였다. 그 결과 emit 직후의setState/apiCall이_eventResult와 리스너가 갱신한_local(예: 업로드된form.images)을 보지 못하고 emit 이전 stale 스냅샷을 사용했다. 이제emitEvent가globalStateUpdater갱신과 동일한 시점에__g7SequenceLocalSync에_eventResult를 병합한_local스냅샷을 실어,handleSequence가 이를 픽업한다. base 는 sequence 가 추적 중인context.state우선이라 in-flightsetState변경(예:isSaving)을 보존하며, sequence 밖(standalone emitEvent)에서는 글로벌_local로 폴백한다. 글로벌_local갱신 동작 자체는 종전과 동일하게 유지된다. (배경: 상품 수정 화면에서 이미지를 추가하고 저장하면 — 저장 sequence 가emitEvent(업로드)→apiCall(PUT)순서인데 — 업로드는 성공해도 PUT body 의form.images가 업로드 전 스냅샷이라 백엔드syncImages가 방금 올린 이미지를 삭제하던 회귀.)- IME(한글·일본어·중국어) 조합 중 Enter 가 액션 키 필터에 매칭되어 글자누락·이중제출 이 발생하던 버그 수정.
ActionDispatcher의 key 필터(action.key지정 keydown 액션)는 비교 전에 IME 조합 상태를 전혀 검사하지 않아, 한글 입력 후 Enter 로 조합을 확정하는 순간 그 Enter keydown 이key:"Enter"액션(검색·제출 등)을 조합 확정 전 값으로 발화시켰다. 결과적으로 (1) 조합 중 마지막 글자가 input 에 커밋되기 전 액션이 실행되어 글자가 누락되고, (2) 액션 실행 후 글자가 커밋되며 두 번째 keydown 으로 재실행되어 이중 제출이 일어났다. 이제isImeComposing헬퍼(event.isComposing === true또는 legacyevent.keyCode === 229검사 — 브라우저별 조합 종료 keydown 차이 흡수)로 조합 중 keydown 은 key 필터 매칭에서 제외한다. 가드는action.key가 지정된 keydown 액션에만 적용되어, key 필터 없는 매 입력 setState 류 액션은 조합 중에도 종전대로 동작한다(회귀 0). 모달 오버레이 ESC 닫기 리스너에도 동일 가드를 적용해 조합 중 ESC 가 모달을 닫지 않도록 일관화했다. (배경: 상품·게시판 검색창에서 한글 검색어 입력 후 Enter 시 마지막 글자가 빠지거나 검색이 두 번 실행되던 결함 — gnuboard/g7 #54.) - 본인인증(IDV) challenge 가 부가 목적(성인인증 등) 미달로 실패할 때 토스트가 2개 중복 표출되던 버그 수정. 본인확인 자체는 성공했으나 부가 조건(만 19세 이상)을 충족하지 못해 challenge 가 실패하면, provider 가 "성인 인증이 필요합니다" 같은 고유 사유를 토스트로 표출한다. 그 직후 코어가 원 428 응답을 onError 로 흘려보내, 원 요청(글쓰기 등)의 generic 가드 토스트("본인 확인이 필요합니다")가 한 번 더 떴다. 이제
IdentityGuardInterceptor에 1회성 도메인 안내 신호(markDomainNoticeShown()/consumeDomainNoticeShown())를 신설하고, provider 가 "본인확인 성공 + 부가목적 미달" 실패를 안내한 직후 이 신호를 남기면, 코어handleToast가 동일 사이클의 generic IDV 가드 토스트(error 타입 +error_code='identity_verification_required') 1건을 skip 한다. 일반 본인인증 실패/취소(본인확인 자체 실패)는 신호가 없어 가드 토스트가 그대로 유지된다 — provider 무지식의 범용 신호이므로 부가 목적이 추가되어도 코어 수정 없이 동작한다. onError 정리 액션(setState{isSaving:false}등)은 그대로 실행되어 버튼 잠금 해제 등 후속 처리는 보존된다.handle()진입 시 이전 사이클의 미소비 신호를 정리해 stale skip 을 방지한다.ErrorContext에error_code필드를 추가하고 onError 컨텍스트에 응답의error_code를 노출한다. (배경: 성인인증이 필요한 게시판 글쓰기에서 미성년자가 인증 시 "성인 인증이 필요한 서비스입니다"와 "본인 확인이 필요합니다"가 동시에 뜨던 결함.) - 레이아웃 편집기 공통(base) 레이아웃 편집 모드에서 모듈이 슬롯에 주입한 UI(예: 헤더 통화·배송국가 셀렉터)가 소비처(SlotContainer) 위치가 아니라 주입 앵커 원위치에 표시되던 결함 수정.
slot노드는 SlotContext 활성 시 원위치에서 렌더되지 않고 SlotContainer 가 떙겨 렌더하는데, base 단독 편집 캔버스는 슬롯이 통째로 사라지는 것을 막으려 모든slot노드를 표시 마커(__editorSlotName)로 치환해 원위치에 렌더했다. 주입 앵커는 보통 헤더 밖 최상단 등 SlotContainer 와 다른 자리라, 통화 셀렉터가 헤더 위 별도 박스로 떠 운영 화면과 어긋났다. 이제 변환 로직(buildBaseEditorComponents)이 같은 base 레이아웃 안에 소비처(SlotContainerprops.slotId) 가 있는 슬롯은 치환하지 않고slot키를 보존해 슬롯 메커니즘에 위임한다 → SlotContainer 가 실제 위치(헤더 안)에서 렌더한다. 소비처가 없는 슬롯(자식 라우트 레이아웃이 채우는content등)만 종전대로 원위치 마커로 치환해 점선 박스로 표시한다. 동일 슬롯을 쓰는 SlotContainer JSON 노드가 하나라도 있으면(데스크톱/모바일 페어 등) 통짜 TSX 컴포넌트 내부 SlotContainer 도 정상 수신하므로, 어떤 템플릿/모듈이 어떤 슬롯에 주입하든 코어 차원에서 동일하게 동작한다. route 편집 모드는 조기 반환이라 영향 0. (배경: 관리자/사용자 템플릿 공통 레이아웃 편집에서 통화·배송국가 선택기가 헤더 위 별도 박스로 분리 표시되던 결함.) - iteration(반복 렌더링) 안의 노드
id에 쓴 표현식(id: "item_{{$idx}}"등)이 보간되지 않고 리터럴 그대로 DOM 에 출력되어, 반복 행마다 같은 HTML id 가 중복되던 결함 수정. 노드의 최상위id필드는props가 아니라 props 바인딩 경로를 타지 않아,DynamicRenderer의 두 DOM 출력 지점(Fragment 컨테이너 div / 일반 컴포넌트 props)이effectiveComponentDef.id를 보간 없이 그대로 내보냈다. 이제id에{{가 포함될 때만 iteration 컨텍스트($idx/item_var포함)로 문자열 보간한resolvedComponentId를 두 DOM 출력 지점이 사용한다(정적 id 는 원본 그대로 — 무영향). slot 등록 키·React remount 키·DevTools 식별자 등 내부 식별에는 원본effectiveComponentDef.id를 계속 사용해 격리한다. 코드베이스 전역 68개 레이아웃이 id 표현식을 이미 쓰고 있었으나 보간 부재로 깨져 있었고, 본 수정으로 일괄 정상화된다. (배경: 관리자 대시보드의 활동 로그·모듈/플러그인/템플릿 카드 등 반복 목록에서 같은 HTML id 가 행마다 중복 마운트되던 결함.) - 레이아웃 편집기 "요소 ID" 컨트롤(
CoreIdControl)이 id 값에 데이터바인딩({{...}})이 있으면 읽기전용으로 잠가 편집할 수 없던 동작을, 데이터 칩 편집 허용으로 변경. iteration 안에서id: "item_{{$idx}}"처럼 반복 인덱스/행 키를 붙여 항목별 고유 id 를 만들려는 정당한 용도를 막던 제약이었다. 이제 칩 포함 시 평문+칩 혼합 편집기(BindingChipTextInput)를 열고, 정적 id 면 후보가 있을 때[🔗 데이터]진입점을 노출한다. HTML id 안전 문자 제약은 평문 세그먼트에만 적용하고{{...}}칩 토큰은 보존한다. 데이터 칩 후보 풀(buildBindingCandidates)에 iteration 변수($idx등 반복 인덱스·행 데이터 필드)를 추가해, 반복 목록 안의 노드를 편집할 때{{$idx}}/{{row.id}}를 후보로 골라 고유 식별자를 만들 수 있다. - 같은 슬롯 컴포넌트가 여러 SlotContainer(헤더 데스크톱/모바일 페어)에서 렌더될 때, 슬롯에 주입된 컴포넌트의 정적 root id 가 컨테이너마다 같은 값으로 중복 출력되어 HTML id 유일성을 위반하던 결함 수정.
SlotContainer가 주입 컴포넌트 root id 를 컨테이너 고유 id 로 스코프({id}__{containerId})해 고유화한다(컨테이너 id 또는 컴포넌트 id 가 없으면 무영향). 이로써 이커머스 헤더 통화·배송국가 셀렉터가 관리자/사용자 헤더의 데스크톱·모바일 SlotContainer 양쪽에 마운트되며 같은 id 로 중복되던 문제가 해소된다. - placeholder 핸들러 친화 동작(핸들러명을 데이터 칩으로 받는 동작)이 "+동작 추가"·핸들러 변경 시 "알 수 없는 동작"(고급)으로 강등되던 결함 수정. 핸들러명 자체를 데이터(응답값 등)로 연결하는 동작 레시피(
build.handler가{{paramKey}}placeholder)는 빈 값으로 추가하면buildAction이 그 토큰을 미입력으로 떨궈handler키 자체가 사라졌고, 핸들러 필드를 다른 데이터 칩으로 바꾸면 구조 식별용 필수 토큰(params)까지 함께 사라져 친화 카드 매칭(matchAction)이 깨졌다. 그 결과 추가 직후 또는 핸들러를 데이터로 연결한 직후 카드가 [고급]으로 떨어져 코드 편집으로만 수정 가능했다. 이제buildAction이 ① placeholder 핸들러가 미입력으로 사라지면 build 의 placeholder 토큰을 복원하고 ② placeholder 핸들러 레시피의 필수(required) 입력 토큰도 미입력 시 보존하며,matchAction의 placeholder 구조 가드는 핸들러가 build 의 placeholder 토큰과 글자 그대로 같은 빈 카드도 인정한다 — 핸들러를 어떤 데이터 칩으로 바꿔도 친화 카드가 유지되고, 저장·새로고침 후에도 친화 카드로 복원된다. 임의의 동적 핸들러 동작(필수 토큰 부재)이 이 레시피로 잘못 흡수되는 것은 그대로 차단된다(greedy 방지 유지). 일반 레시피(리터럴 핸들러)는 무영향(미입력 키 그대로 정리). 데이터/오류/수신 동작 입력의 응답 데이터 칩 후보를 확장 editor-spec(actionChipCandidates)이 도메인 응답 필드까지 더할 수 있게 일반화해, apiCall 후속 동작 등에서 확장이 선언한 응답 필드를 데이터 칩으로 연결할 수 있다(코어는 도메인 무지·확장이 선언). (배경: 결제(PG) 진입 동작을 응답이 지정한 핸들러로 dispatch 하도록 만들면서, 핸들러명을 데이터로 연결하는 동작 일반이 편집기에서 친화 편집되지 않던 결함.)
Removed
- MVP 위지윅 잔재 정리 — 캔버스 상단 페이지 제목 배너(더블클릭 인라인 편집·표현식 고급 배지 폐기, 페이지 설정 모달로 진입점 통합), 동작 카드 ▲▼ 순서 버튼(드래그 손잡이로 대체), 데이터 소스 [기본값] raw JSON 직접 입력 모드(블럭 편집으로 통일).
- 코어 빌트인 icon-picker 위젯·
IconPickerControl(라이브러리 종속 → 템플릿 소유로 이양), 코어 색/아이콘 토큰 어휘(전부 템플릿 editor-spec 카탈로그 공급).
[engine-v1.49.3] - 2026-05-22
Removed
- (engine-v1.49.3) MVP 위지윅 편집기 제거 —
?mode=edit쿼리 기반 편집 모드 분기,renderWithWysiwygEditor·checkEditMode·getTemplateIdFromUrl헬퍼,G7Core.wysiwyg전역 API(initWysiwygEditorAPI) 삭제. 신규 레이아웃 편집기로 대체 예정 (template-engine.ts, G7CoreGlobals.ts)?mode=editURL 진입 시 일반 렌더로 폴백.DynamicRenderer의componentPath/data-editor-path범용 편집 인프라는 보존
[engine-v1.49.2] - 2026-05-07
Fixed
- (engine-v1.49.2)
_localInit useLayoutEffect의currentPendingbaseline 이 비어있을 때 globalState 의 직전 setState 결과(예: init_actions 가 박은tempKey)를 흡수하지 못해 후속 setLocal mergedPending 의pendingState || baseLocal분기에서 stale pending 우선 사용 → globalState._local 통째 교체 시 손실 키 영구 누락되던 회귀 수정 (DynamicRenderer)- 증상: 게시판 글쓰기 화면(board/form) 직접 URL 진입 또는 강제 새로고침 시 init_actions setState 가 globalState._local 에 박은
tempKey가 사라져 첨부파일 업로드용temp_key빈 값 평가. 목록 → 글쓰기 navigate 진입은 정상. 3회 중 1회 빈도로 간헐 발생 - 원인: setState_3 (init_actions tempKey) → globalStateUpdater 경로 →
globalState._local = {form, tempKey}정상. 그러나 React 마운트 후 useLayoutEffect 클리어로__g7PendingLocalState = null→ _localInit useLayoutEffect 가 발화 시currentPending = null || {} = {}(빈 baseline) → pendingNext ={form, hasChanges:false}(tempKey 미포함, stale). 이후 performStateUpdate → setLocal({render:false}) → mergedPending 의pendingState || baseLocal분기로 stale pending 우선 사용 → globalState._local 통째 교체 시 tempKey 손실 - 수정:
currentPendingbaseline 을__g7PendingLocalState || {}→{...G7Core.state.get()._local, ...(__g7PendingLocalState || {})}로 변경. fresh globalState._local 위에 기존 pending 머지로 baseline 이 진짜 fresh 보장. engine-v1.27.0 의 useLayoutEffect 사전 동기화 의도 보존 + 강화 - 회귀 테스트:
troubleshooting-state-setstate.test.ts[사례 36](8개)
- 증상: 게시판 글쓰기 화면(board/form) 직접 URL 진입 또는 강제 새로고침 시 init_actions setState 가 globalState._local 에 박은
[engine-v1.49.1] - 2026-05-06
Fixed
- 인라인
$t:defer:key|...가 다른 텍스트와 혼용될 때 (예:{{id}} — $t:defer:key|x=y) 키 문자 클래스가 콜론을 미포함해$t:defer만 매칭되고 나머지가 raw 로 노출되던 회귀 수정 —TRANSLATION_PATTERN에 옵셔널(?:defer:)?추가 (TranslationEngine)
[engine-v1.49.0] - 2026-05-05
Changed
TemplateApp.reloadExtensionState()가 활성 로케일 목록(_global.appConfig.supportedLocales)도/api/locales/active응답으로 갱신하도록 확장 — 언어팩 설치/활성화/제거 시reloadExtensions핸들러 호출만으로 사용자 언어 셀렉터(UserProfile 등)가 새로고침 없이 즉시 반영. 기존 cache_version + routes + layout + translations 재로드 단계 뒤에 추가 (TemplateApp)refresh핸들러에params.delayMs옵션 추가 — 선행 토스트/모달 닫힘 애니메이션이 인지된 후 reload 되도록 지연 가능. 기본값 0 으로 기존 호출처 동작 동일 (ActionDispatcher)
Added
- 신규 공개 API
GET /api/locales/active— 활성 코어 언어팩 +config('app.supported_locales')합집합 반환 (LocaleController, LanguagePackService::getActiveLocales)
[engine-v1.48.0] - 2026-04-30
Changed
$locales전역 변수가 시스템 활성 언어팩(_global.appConfig.supportedLocales)을 우선 반영하도록 의미 변경 — 언어팩 설치/제거 시 사용자 정보 수정·헤더 언어 선택·다국어 입력 탭 등 모든 picker UI 가 즉시 반영. 미초기화 시에만 템플릿 정적 메타데이터(template.jsonlocales)로 폴백. 백엔드Rule::in(config('app.supported_locales'))검증과 정합 (template-engine, G7CoreGlobals)
Added
$templateLocales전역 변수 신설 — 현재 템플릿이 자체 번역을 제공하는 언어 목록(template.jsonlocales). 템플릿 정적 메타데이터가 필요한 경우 사용 (template-engine)
[engine-v1.47.1] - 2026-04-30
Fixed
TemplateApp.showRouteError의 401 가드가 토큰 보유 여부 판정에서apiClient.getToken()만 사용해 토큰 만료 사용자에게 안내 토스트가 노출되지 않던 문제 수정 —LayoutLoader가 401 시 토큰을 자동 제거하고 재시도하므로 가드 진입 시점에는 항상 토큰이 null.LayoutLoader가 첫 401 시 토큰 보유 상태였음을LayoutLoaderError.details.hadToken=true로 마킹해 가드로 전달. 가드는 (현재 토큰 보유 ORdetails.hadToken === true) 일 때reason='session_expired'부여. 익명 방문자 진입은 마킹이 없어 reason 미부여 (정책 유지: 한 번도 로그인하지 않은 사용자에게 "세션 만료" 안내 차단) ( 후속, LayoutLoader · TemplateApp)apiClient.setOnUnauthorized콜백이 데이터소스 401 시 로그인 페이지로 redirect 할 때reason인자를 전달하지 않아 안내 토스트 트리거가 누락되던 문제 수정 — 콜백 발동은 토큰이 서버에서 거부되었음을 의미하므로 항상reason='session_expired'부여. layout fetch 401 가드와 데이터소스 401 콜백 두 경로 모두 동일한 안내 흐름으로 통일 ( 후속, TemplateApp)
Notes
- 본 패치 디버깅 과정에서 발견된 G7 표현식 컨텍스트 규약 명문화: URL query string 은 root 컨텍스트에
query.xxx/query?.xxx로 직접 노출되며route.query경로는 존재하지 않음.route.xxx는 path params 만 (예:/users/:id→route.id). docs/frontend/data-binding.md 에 회귀 차단 가이드 추가
Notes
- 본 패치는 init_actions/액션의
if표현식 작성 패턴을 명확히 하지는 않으나 관련 회귀 차단 회기에 함께 추가됨:if값은ConditionEvaluator.evaluateStringCondition이 평가하며,{{}}외부 텍스트는 보간되지 않고 그대로 문자열로 남아Boolean()판정 시 항상 truthy 가 된다. 비교/논리 연산이 포함된 식은 반드시 전체를{{}}한 쌍으로 감싸야 식으로 평가된다 (예:"{{x === 'y'}}"). 이는 docs/frontend/data-binding.md / layout-json-features.md 가이드에 명시되어 있으며, 본 릴리즈에서 문서 측 보강은 진행하지 않음 (audit 룰 후속 advisory)
[engine-v1.47.0] - 2026-04-29
Added
- 레이아웃 fetch 401 재시도 실패 시 코어가 로그인 페이지로 자동 리다이렉트 () — 토큰 만료/권한 부족으로 레이아웃을 받지 못한 사용자가 "페이지 로딩 실패" 에러 화면 대신 로그인 화면으로 이동하고
?reason=session_expired쿼리로 안내 토스트가 트리거됨. 인증 타입은templateId또는 pathname 의/adminprefix 로 결정 (TemplateApp.showRouteError) AuthManager.getLoginRedirectUrl(type, returnUrl, reason?)세 번째 옵셔널 인자 —reason='session_expired'등 사유를 쿼리 파라미터로 결합. 기존 호출처는 인자 미지정으로 하위호환 (AuthManager)AuthManager.updateConfig(type, partial)public setter — 템플릿 부트스트랩에서loginPath등 인증 설정을 사이트 단위로 커스터마이즈 가능. 보안:loginPath는 동일 origin path-only(/로 시작,//금지)만 허용 (open redirect 차단 — 외부 origin/protocol-relative URL 은 throw). 모듈/플러그인 호출은 다른 템플릿 침범 위험으로 가이드 문서에서 금지 (AuthManager)
[engine-v1.46.1] - 2026-04-28
Fixed
- (engine-v1.46.1)
IdentityGuardInterceptor.handle()retry fetch 가 원 요청의 body/headers/credentials 를 보존하도록 수정 — 두 번째 인자originalRequest추가. 과거 retry 가 빈 body 로 호출되어 회원가입 등 모든 POST 흐름이 IDV verify 통과 후 422 (필수 필드 누락) 로 실패하던 회귀 차단. ActionDispatcher.handleApiCall 가options.body/headers/credentials를 그대로 전달 (IdentityGuardInterceptor, ActionDispatcher)
[engine-v1.46.0] - 2026-04-27
Added
- IDV 공용 타입 모듈
resources/js/core/identity/types.ts—VerificationPayload,IdentityResponse428,VerificationResult(4-상태:verified | pending | cancelled | failed),ModalLauncher,ResolveIdentityChallengeParams,IdentityRedirectStash,IDENTITY_REDIRECT_STASH_KEY정리. 외부 IDV provider 플러그인이 동일 타입을 import 해 사용 resolveIdentityChallenge액션 핸들러 — 본인인증 모달 / 풀페이지 / 외부 SDK callback 이 launcher 의 deferred Promise 에 verify 결과를 통보하는 표준 진입점. params 의result가verified|pending|cancelled|failed4-상태이며 누락/오타는 안전한 기본값으로 강등 처리 (ActionDispatcher)IdentityGuardInterceptor.createDeferred()/resolveDeferred()— launcher 가 모달 결과를 await 하기 위한 deferred resolver API. 두 launcher 가 동시 활성화되면 이전 resolver 는 자동으로cancelled처리 (IdentityGuardInterceptor)IdentityGuardInterceptor.redirectExternally()헬퍼 —external_redirect흐름에서 sessionStorage 에 stash 후window.location.href로 이동. stash 키는 코어 상수IDENTITY_REDIRECT_STASH_KEY로 통일 (IdentityGuardInterceptor)defaultLauncher— launcher 미등록 외부 템플릿용 폴백. 토스트 발행("본인 확인이 필요합니다") +/identity/challenge?return=...navigate. G7Core 미초기화 시console.error+failed/G7_NOT_READY로 강등 (IdentityGuardInterceptor)
Changed
ModalLauncher반환 타입을Promise<VerificationResult>로 확장 (기존Promise<boolean>→ 4-상태 객체). PortOne/Stripe Identity 같은 외부 SDK 가 검증 데이터를 함께 돌려주는 케이스 + Stripe webhook 모델 같은 비동기 검증 인터페이스 예약 (IdentityGuardInterceptor)IdentityGuardInterceptor.handle()가 verify 성공 시return_request.url에verification_token을 query string 으로 자동 부착해 재실행. 회원가입 폼이 이미 사용 중인query.verification_token패턴과 호환 (IdentityGuardInterceptor)
[engine-v1.45.0] - 2026-04-24
Added
- startInterval / stopInterval 액션 핸들러 —
params.id기반 setInterval 등록/중단. 카운트다운 타이머 등 주기적 UI 업데이트에 사용. 같은 id 재등록 시 기존 타이머 자동 정리,stopAllIntervals()로 일괄 중단 가능 (ActionDispatcher)
[engine-v1.44.0] - 2026-04-24
Added
- IdentityGuardInterceptor — HTTP 428
identity_verification_required응답을 감지해 모달 launcher 호출 후 return_request 자동 재실행. launcher 는 S8 공통 모달 레이아웃이 제공. ActionDispatcher.handleApiCall 응답 후처리 한 곳에 정적 메서드로 직접 위임 — 별도 디스패처 인프라 없이 모든 apiCall 의 choke point 효과 (IdentityGuardInterceptor)
[engine-v1.43.1] - 2026-04-25
Fixed
responsive.{breakpoint}.iteration오버라이드 케이스에서 무한 재귀로 worker OOM 발생 —renderIteration이 자식 wrapper 렌더 시componentDefWithoutIteration에iteration: undefined만 적용하고responsive는 유지하여, 자식의effectiveComponentDef머지에서responsive.{bp}.iteration이 다시 적용되어 iteration 이 부활하는 무한 루프.responsive: undefined도 함께 제거하여 머지 1회로 한정 (DynamicRenderer)
[engine-v1.43.0] - 2026-04-22
Fixed
- Form 자동바인딩 값이
globalState._local에 동기화되지 않아 CKEditor5 등setLocal({render:false})플러그인과 공존하는 폼에서 자동바인딩 값이 누락되던 문제 수정.performStateUpdate가 기존 ReactlocalDynamicState쓰기와 함께G7Core.state.setLocal(..., {render:false})로 globalState._local에 동기화 기록. 이중 저장소 구조는 성능상 의도적으로 유지하며, 자세한 배경은 DynamicRenderer.tsxperformStateUpdate상단 주석 참조 (DynamicRenderer)
Added
- 자동바인딩 경로 레지스트리
__g7AutoBindingPaths— Input 마운트 시fullPath를 reference count 기반으로 등록/해제. iteration 내 중복·React Strict Mode 이중 마운트 대응 (DynamicRenderer) G7Core.state.setLocal(..., {render:false})호출이 자동바인딩 경로와 겹치면 엔진이 자동으로 render:true로 승격 — 미래 플러그인이 자동바인딩 대상 필드를 render:false로 쓰더라도 저장소 A↔B 정합성 구조적 보장 (G7CoreGlobals)G7Core.state.setLocal옵션selfManaged: true신설 — 플러그인이 자체 DOM 관리를 의도적으로 선언하는 opt-out 마커. 명시 시 자동 승격 제외하여 render:false 유지(성능 보존). CKEditor5처럼 React 밖에서 DOM을 관리하는 플러그인 전용. 기본값 undefined(=false)는 safe-by-default (G7CoreGlobals)- SPA 네비게이션 시
__g7AutoBindingPaths를 빈Map으로 재초기화 — 이전 페이지 컴포넌트 언마운트와 라우트 전환 경쟁으로 인한 stale 경로 잔존 방지 (TemplateApp) G7DevToolsCore.getDualStorageMismatch()진단 메서드 — 저장소 A(localDynamicState) / B(globalState._local) 불일치 leaf 경로 감지. Phase 1 이후 유지보수 중 쓰기 경로 누락 조기 발견을 위한 보조 안전망 (G7DevToolsCore)
[engine-v1.42.0] - 2026-04-16
Added
render: false선택적 리렌더 제어 — 상태 값은 저장하되 React 리렌더를 건너뛰는 옵션. CKEditor 등 자체 DOM을 관리하는 플러그인에서 타이핑 중 전체 폼 리렌더(37,000+ 바인딩 재평가) 방지.flushPendingDebounceTimers실행 시 항상 render: true 강제로 저장 직전 데이터 정합성 보장 (TemplateApp, G7CoreGlobals, ActionDispatcher)G7Core.state.setLocal(updates, { render: false })— 로컬 상태 사일런트 업데이트G7Core.state.set(updates, { render: false })— 전역 상태 사일런트 업데이트setState핸들러render: false— 레이아웃 JSON 액션 레벨 옵션:{ "handler": "setState", "render": false }
[engine-v1.41.0] - 2026-04-16
Added
G7Core.state.setLocal()debounce/debounceKey옵션 — 프로그래매틱 호출에서 G7 표준 디바운스 사용 가능. ActionDispatcher의 기존 타이머 인프라(자동 정리, flushPendingDebounceTimers 연동)를 활용 (G7CoreGlobals)G7Core.dispatch()debounce/debounceKey옵션 — 액션 시스템 진입점에서도 표준 디바운스 지원 (G7CoreGlobals)ActionDispatcher.debouncedCall()공개 메서드 — 프로그래매틱 debounce용 타이머 관리 (ActionDispatcher)
Fixed
setLocal()내부baseLocal계산 시 staleactionContext.state의 빈 배열이globalLocal의 정상 API 데이터를deepMerge로 교체하는 오염 경로 차단 —deepMerge→addMissingLeafKeys전략으로 변경하여globalLocal(committed 상태)의 기존 값을 보존하면서dynamicLocal(setState 전용 키)만 안전하게 추가. CKEditor 등 플러그인 onMount에서setLocal호출 시 init_actions 기본값(빈 배열)이 API 데이터를 덮어쓰는 문제 해소 (G7CoreGlobals)- SPA 네비게이션 시
_localInit미적용 상태에서 staledynamicState병합 방지 —lastProcessedInitRefref 기반 감지로extendedDataContext병합에서 staledynamicState건너뛰기 (DynamicRenderer)
[engine-v1.40.0] - 2026-04-15
Added
navigate핸들러params.fallback옵션 — 대상 경로가 현재 템플릿의routes.json에 매칭되지 않을 때 대체 핸들러로 분기. 기본값은openWindow(새 창 열기)로, 관리자 ↔ 사용자 템플릿 간 경로 교차 이동 시 404 페이지 대신 새 창으로 열림.fallback: false로 비활성화 가능,fallback: "핸들러명"또는fallback: { handler, params }로 커스텀 지정 가능.replace: true분기(쿼리 갱신 전용)에는 미적용. 알림센터에서 알림 클릭 시 교차 템플릿 경로 접근 문제 해결 (ActionDispatcher)
[engine-v1.39.0] - 2026-04-15
Changed
- 확장 에셋 로딩 병렬화 —
ModuleAssetLoader.loadActiveExtensionAssets()가 JS 번들을for...await로 직렬 fetch 하던 것을Promise.all(map(loadJS))병렬 fetch 로 전환. 실행 순서는script.async = false+ priority 정렬 순서로 DOM append 되어 HTML 사양에 따라 그대로 보장됨. 사용자 화면 진입 시 5개 IIFE 확장 기준 staircase 로딩이 ~1.3초 → ~350ms 로 단축. 코드베이스 감사 결과 cross-extension 참조·priority 차등·등록 순서 의존성 모두 없음을 확인한 후 적용 (ModuleAssetLoader)
[engine-v1.38.2] - 2026-04-15
Fixed
- (engine-v1.38.2)
{{$event.target.checked ? '$t:admin.modules.activate_success' : '$t:admin.modules.deactivate_success'}}같은 조건부 표현식 안의 따옴표$t:패턴이 raw key 를 토스트로 노출하던 버그 —DataBindingEngine.preprocessTranslationTokens()가 따옴표 안의'$t:KEY'를$t('KEY')함수 호출로 변환하고,$t()헬퍼가context.$templateId를 templateId 로 사용하는데, ActionDispatcher 의createHandler()가 빌드한 action data context 가 항상$templateId를 포함하지는 않아 빈 templateId 로 lookup 이 실패하던 문제.$t()가 이제context.$templateId가 없으면window.__templateApp.getConfig()로부터 templateId/locale 을 회수한다 (DataBindingEngine). admin_module_list / admin_plugin_list / admin_role_list 등 onSuccess 토스트 메시지가 다시 정상 번역됨
[engine-v1.38.1] - 2026-04-15
Fixed
- (engine-v1.38.1)
reloadExtensions병렬 블록에서 동시에 실행되는toast($t:...)가 raw 다국어 키(예:admin.modules.activate_success)를 그대로 노출하던 경합 —TranslationEngine.setCacheVersion()내부의clearCache()호출이 활성this.translations맵을 비워서, 새loadTranslations()가 fetch를 완료하기 전에 실행되는translate()호출이 빈 사전을 만나 폴백 규칙(3번째 단계)에 따라 원본 키를 반환하던 문제.setCacheVersion()이 이제 TTL 캐시(this.cache)만 비우고 활성 사전(this.translations)은 그대로 유지 —loadTranslations()완료 시점에translations.set(cacheKey, dictionary)로 원자 교체되므로 레이스 윈도우가 제거됨.TemplateApp.reloadExtensionState()도 명시적clearCache()호출을 제거. 유사 사례 전수 적용(레이아웃 수정 불요) — 모든$t:해석 경로가 동일 보호를 받음 (TranslationEngine, TemplateApp)
[engine-v1.38.0] - 2026-04-15
Added
reloadExtensions통합 핸들러 — 확장(모듈/플러그인/템플릿)의 install/activate/deactivate/uninstall 직후 onSuccess 체인에서 호출하여 페이지 전체 새로고침 없이 확장 상태를 원자적으로 재동기화. 내부적으로TemplateApp.reloadExtensionState()를 호출하여 최신cache_version획득 → localStorage 반영 → Router routes 재fetch → LayoutLoader 캐시 클리어 → TranslationEngine 재로드까지 일괄 수행. 선택 파라미터{ moduleInfo, pluginInfo, action: "add"|"remove" }전달 시reloadModuleHandlers/reloadPluginHandlers로직도 통합 실행하여 JS/CSS 에셋을 동적으로 로드/제거 (ActionDispatcher)TemplateApp.reloadExtensionState()public 메서드 — config.json 에서 새 cache_version 획득 후 Router/LayoutLoader/TranslationEngine 을 순차 갱신. 각 단계는 try/catch 로 격리되어 한 단계 실패가 다른 단계를 막지 않음 (TemplateApp)Router.loadRoutes(cacheVersion?: number)파라미터 — 전달 시?v=${version}쿼리로 부착되어 백엔드PublicTemplateController::getRoutes의 응답 캐시 버전 키를 일치시킴. 미전달 시 구 버전 동작 유지 (Router)
Fixed
- 확장 활성화/비활성화 후 전체 새로고침 없이는 새 라우트가 반영되지 않던 버그 —
Router.loadRoutes()가 캐시 버전 쿼리 없이/api/templates/{id}/routes.json을 요청하여 백엔드의template.routes.{id}.v0캐시 키에 과거 응답이 고정되거나 TTL(1시간) 동안 오염된 상태가 재사용되던 문제.reloadExtensions핸들러가 최신cache_version으로 fetch 하도록 수정하여 activate API 완료 시점에 즉시 routes 가 반영됨 (Router, TemplateApp, ActionDispatcher)
Deprecated
reloadRoutes핸들러 —reloadExtensions로 대체 예정. 현재는TemplateApp.reloadExtensionState()로 위임하여 하위 호환 유지 (ActionDispatcher)reloadTranslations핸들러 —reloadExtensions로 대체 예정. 현재는TemplateApp.reloadExtensionState()로 위임 (ActionDispatcher)
[engine-v1.37.0] - 2026-04-14
Added
navigate핸들러params.scroll/params.scrollBehavior옵션 — 페이지 이동 후 스크롤 위치를 선언적으로 제어. 지원 값:"top"(기본) /"preserve"/ 숫자 /{x, y}/"#selector".requestAnimationFrame으로 다음 tick에 적용하여 새 레이아웃 DOM 반영 후 정확한 위치로 이동.replace: true분기(updateQueryParams)에도 동일하게 적용.scrollBehavior기본값은"instant"— 템플릿 CSS의scroll-behavior: smooth전역 설정을 무시하고 페이지 전환 시 즉시 이동하도록 강제."smooth"명시 시에만 부드러운 스크롤 (ActionDispatcher)replaceUrl핸들러params.scroll/params.scrollBehavior옵션 — 기본값은"preserve"(URL만 교체하는 용도이므로 스크롤 유지). 명시적으로"top"등 지정 가능 (ActionDispatcher)scroll: "top"동작 시window뿐 아니라#app내부의 모든 스크롤 컨테이너(overflow-y: auto|scroll또는overflow-x: auto|scroll)를 함께 상단으로 리셋 — 관리자 템플릿처럼html/body가overflow: hidden이고 내부 div가 실제 스크롤 컨테이너인 구조에서도window.scrollTo만으로는 효과가 없어 스크롤이 이동하지 않던 문제 해결 (ActionDispatcher)scroll확장 객체 문법 —{ container?, to?, block?, offset? }형태로 특정 스크롤 컨테이너 지정, sticky 헤더 오프셋,scrollIntoView의block위치(start/center/end/nearest) 선택을 지원. 관리자 템플릿의#right_content_area같은 내부 스크롤 컨테이너에 대해 Y 좌표 이동, 엘리먼트로 이동(중앙/상단/하단 정렬), 오프셋 보정이 가능 (ActionDispatcher)
Changed
- Breaking:
navigate핸들러 기본 스크롤 동작 변경 — 기존에는 페이지 이동 후 이전 스크롤 위치가 그대로 유지되어 일반적인 하이퍼링크 이동 UX와 어긋났음. 이제 기본값"top"으로 이동 시 최상단에서 새 페이지가 시작됨. 스크롤 유지가 필요한 케이스(검색 필터, 페이지네이션 등)는scroll: "preserve"명시 필요 (ActionDispatcher)
[engine-v1.36.0] - 2026-04-13
Added
navigate핸들러params.transition_overlay_target옵션 —replace: true로updateQueryParams경로 진입 시transition_overlay.target을 호출별로 동적 override. 환경설정 알림 탭 안의 채널 탭 등 탭 속 서브 탭 클릭 시 서브 탭 콘텐츠 영역에만 spinner 가 표시되도록 한다. 미지정 시transition_overlay.target(베이스 또는 자식 merge 결과) 사용 (ActionDispatcher, G7CoreGlobals, TemplateApp)G7Core.updateQueryParams(newPath, options?)— 두 번째 인자options.transitionOverlayTarget로 호출별 target override 지원 (G7CoreGlobals, TemplateApp)
[engine-v1.35.0] - 2026-04-13
Added
updateQueryParams경로에서transition_overlayspinner/skeleton 자동 트리거 —navigate replace:true로 탭 전환/검색/페이지네이션 시 handleRouteChange 대신 updateQueryParams 경로에 진입할 때도blocking또는wait_for에 명시된 progressive 데이터소스가 1개 이상 refetch 대상이면 오버레이 표시. fetch 완료/에러 시 자동 hide. handleRouteChange step 2.5 와 동일 정책 (TemplateApp)
[engine-v1.34.0] - 2026-04-13
Added
transition_overlay.wait_for옵션 — blocking 데이터소스가 없는 페이지에서도 명시된 progressive 데이터소스가 fetch 완료될 때까지 spinner/skeleton 오버레이가 표시되도록 하는 명시적 가드. background/websocket 데이터소스는 의도상 사용자 차단 불가하므로 자동 무시되며 백엔드 검증(UpdateLayoutContentRequest)에서 사전 차단됨 (TemplateApp, LayoutLoader 타입)
Changed
LayoutService::mergeLayouts의transition_overlay병합을 shallow merge 로 변경 — 자식 레이아웃이wait_for만 명시해도 부모(베이스)의enabled/style/target/spinner설정이 보존되어 자식이 부분 override 가능. boolean/비배열 케이스는 기존 자식 우선/부모 폴백 유지 (LayoutService)
[engine-v1.33.1] - 2026-04-10
Fixed
- WebSocket 리스너 중복 누적 버그 수정 —
WebSocketManager.unsubscribe()가subscriptionsMap에서만 엔트리를 제거할 뿐 Echo 채널의listen()리스너를 해제하지 않아, route 변경 시 재구독 시 Echo가 동일 채널 인스턴스를 재사용하며 listener가 누적됨. 결과: 단일 route에서 알림 폼 저장 등으로 route가 여러 번 전환되면 N번 중복 수신 (예: 수정 창에서 비밀번호 변경 알림 토스트 8번 실행).channelInstance.stopListening('.${event}')명시 호출로 수정 (WebSocketManager.ts)
[engine-v1.33.0] - 2026-04-10
Added
- WebSocket 데이터소스에
onReceive액션 배열 지원 — 메시지 수신 시 정의된 액션들을 순차 실행.$args[0]또는$event로 페이로드 접근 가능. refetchDataSource/toast 등 모든 핸들러 사용 가능. 사용 예시: 알림 수신 시notification_unread_count자동 refetch + 토스트 표시 (DataSourceManager.ts, TemplateApp.ts)
[engine-v1.32.5] - 2026-04-09
Fixed
updateQueryParams데이터소스 refetch 인덱스 매핑 어긋남 — WebSocket 소스가currentDataSources에 포함된 레이아웃(예:_admin_base.json의notification_ws)에서navigate replace:true(탭 전환 등) 호출 시fetchDataSourcesWithResults가 WebSocket을 내부 필터링하여 results 배열이 짧아지는데 호출자는autoFetchDataSources[i]로 매핑해 데이터가 잘못된 키에 기록됨. 증상: 환경설정/알림설정 탭 클릭 시_local.form이 다른 데이터소스 응답으로 초기화되어 탭 내용이 빈 화면 (TemplateApp)- 원인 1:
updateQueryParams가 인덱스 기반for (let i = 0; i < results.length; i++) { autoFetchDataSources[i] }매핑 사용 →handleRouteChangeblocking 경로(results.forEach((r) => blockingData[r.id] = r.data))와 일관성 없음 - 수정 1:
updateQueryParams를result.id기반 Map 조회로 변경하여 handleRouteChange 패턴과 통일 (TemplateApp) - 원인 2:
updateQueryParams가 WebSocket 소스를 사전 제외하지 않아fetchDataSourcesWithResults내부 silent filter와 계약 불일치 - 수정 2:
updateQueryParams에서 WebSocket 사전 제외 추가 — handleRouteChange progressive 경로(engine-v1.32.2)와 동일 정책 (TemplateApp) - 원인 3:
fetchDataSourcesWithResults의 내부 WebSocket 필터가 silent — 호출자가 WebSocket을 넘겨도 경고 없이 조용히 제거되어 인덱스 매핑 어긋남 버그가 숨겨짐 - 수정 3:
fetchDataSourcesWithResults에서 WebSocket 소스 수신 시 경고 로그 추가 — 내부 필터는 safety net으로 유지하되 호출자 필터 누락을 조기 발견 가능 (DataSourceManager)
[engine-v1.32.4] - 2026-04-09
Fixed
- WebSocket 채널 빈 세그먼트 검증 강화 — 표현식이 undefined로 평가되어 빈 문자열로 치환되면 trailing dot/연속 dot이 발생해도 미평가 마커(
{{)가 없어 방어 로직을 우회.isInvalidChannel헬퍼 추가하여 trailing/leading/연속 빈 세그먼트(.,:)도 감지 후 구독 건너뜀. 진단을 위해 컨텍스트 키 목록을 경고 로그에 포함 (DataSourceManager) - WebSocket 구독 직전 컨텍스트 키 디버그 로그 추가 — 표현식 평가 실패 진단용 (TemplateApp)
[engine-v1.32.3] - 2026-04-09
Fixed
- WebSocket 채널/이벤트 표현식 평가 누락 —
core.user.notifications.{{current_user.data.id}}같은 표현식이 평가 없이 그대로 구독되어 백엔드 채널 패턴 매칭 실패 → broadcasting/auth가 AccessDeniedHttpException 발생.subscribeWebSockets에bindingContext파라미터 추가, channel/event 모두resolveExpressionString으로 평가 (DataSourceManager) - WebSocket 구독 시점이 progressive fetch 이전이라 fetched 데이터 참조 표현식 평가 불가 — Step 6 WebSocket 구독을 progressive fetch 완료 후(Step 7)로 이동하여 모든 데이터소스가 평가 컨텍스트에 포함되도록 함 (TemplateApp)
- WebSocket 미평가 표현식 방어 로직 — 평가 후에도
{{마커가 남아있으면 구독 건너뛰고 경고 로그 (DataSourceManager)
[engine-v1.32.2] - 2026-04-09
Fixed
- WebSocket 데이터소스가 progressive 목록에 포함되어 blur_until_loaded 영구 블러 — WebSocket은 이벤트 리스너이지 데이터 제공자가 아니므로 progressive 초기화에서 제외 (TemplateApp)
[engine-v1.32.1] - 2026-04-09
Fixed
- auth_mode: 'required' 데이터소스에서 토큰 없을 때 API 요청 스킵 — 비로그인 상태에서 인증 필수 API 호출 → 401 → onUnauthorized 로그인 리다이렉트 무한 루프 방지 (DataSourceManager)
[engine-v1.32.0] - 2026-04-08
Added
- extensionPointCallbacks — extension_point에서 콜백 액션 객체를 props와 분리하여 전달하는 기능 추가 (DynamicRenderer, LayoutExtensionService)
props: 데이터 전달용 —resolveObject()로 표현식 재귀 평가 (일반 props와 동일 수준)callbacks: 액션 객체 전달용 — 평가 없이 그대로 전달 (ActionDispatcher 실행 시점 평가)
Fixed
- extensionPointProps 표현식 미평가 버그 수정 — 호스트가
"{{route.id}}"같은 표현식을 넘기면 평가 없이 raw 문자열로 전달되던 문제 해결 (DynamicRenderer)
[engine-v1.31.0] - 2026-04-02
Added
- resolvedProps 참조 안정화 — 바인딩 해석 결과의 값이 이전과 동일하면 이전 객체 참조를 반환하여 하위 컴포넌트의 React.memo가 실제로 작동하도록 개선 (DynamicRenderer)
- ComponentRegistry에서 컴포넌트 등록 시 React.memo 자동 래핑 — props 변경이 없는 컴포넌트의 불필요한 리렌더링 방지 (ComponentRegistry)
Fixed
- DevTools 캐시 탭 Hit Rate 카드와 프로그레스 바에서 hitRate(0~1)를 백분율 변환하지 않아 0.9%로 표시되던 버그 수정 (CacheTab)
[engine-v1.30.0] - 2026-04-02
Added
- SortableItemWrapper에
asprop 추가 — wrapper 요소를 지정 가능 (기본:div, Table 내부:tr) - DynamicRenderer에서 sortable 설정의
wrapperElement옵션을 SortableItemWrapper에 전달
[engine-v1.29.2]
Fixed
- (engine-v1.29.2)
if조건에서{{true}},{{false}},{{null}},{{undefined}}리터럴이 컨텍스트 경로로 해석되어 항상 undefined 반환되는 버그 수정 (ConditionEvaluator, RenderHelpers)- 원인:
isComplexExpression체크에서 연산자 없는 단순 문자열로 판정 →resolve("true")→context["true"]→undefined - 영향 범위:
evaluateStringCondition(ConditionEvaluator) +evaluateIfCondition(RenderHelpers) 양쪽에 동일 버그 존재 - 수정: ConditionEvaluator에
resolveLiteral(), RenderHelpers에LITERALSMap 추가 —resolve()호출 전에 JS 리터럴을 직접 반환 - SEO 엔진(
ExpressionEvaluator.php)은 이미 리터럴 체크가resolvePath()전에 있어 수정 불필요
- 원인:
[engine-v1.29.1]
Fixed
- (engine-v1.29.1) 에러 페이지(404 등)에서 로그인 상태가 표시되지 않는 버그 수정 (ErrorPageHandler)
- 원인:
renderError()가 data_sources fetch 후initGlobal/initLocal처리를 하지 않아_global.currentUser가 undefined로 남음 - 수정:
processInitOptions()메서드 추가 — fetch된 데이터의initGlobal/initLocal옵션을 처리하여_global/_local상태에 매핑 - 지원 형태: 문자열(
"currentUser"), 배열(["user", "profile"]), 객체({ key, path })
- 원인:
[engine-v1.29.0]
Added
- transition_overlay
spinner스타일 — 커스텀 로딩 컴포넌트 지정 가능 (TemplateApp)- 엔진은 빈 컨테이너 + position:relative CSS만 제공 — 비주얼 스타일은 컴포넌트가 전적으로 결정
- 3단계 fallback chain: target → fallback_target → #app (fullpage)
spinner.component: 컴포넌트 레지스트리에서 로딩 컴포넌트 조회 (예: PageLoading)- 컴포넌트 미지정 시 CSS 커스텀 속성 기반 기본 스피너 폴백
- renderTemplate 후
reattachSpinnerOverlay()로 새 DOM 타겟에 재마운트
- LayoutLoader 타입 정의에
spinner스타일 및 spinner config 타입 추가 (LayoutLoader) - UpdateLayoutContentRequest에
spinnervalidation 규칙 추가
Fixed
- (engine-v1.29.0) spinner overlay 번역 미동작 수정 — renderSpinnerOverlay에서 G7Core.t() 사전 해석 후 컴포넌트에 전달 (TemplateApp)
- (engine-v1.29.0) spinner overlay 하드코딩 스타일 제거 — 배경색/z-index/포지셔닝을 엔진에서 제거하고 컴포넌트 책임으로 이전 (TemplateApp)
[engine-v1.28.1]
Fixed
- cellChildren 표현식 평가 결과
$t:번역 후처리 누락 수정 (RenderHelpers)- 증상:
{{row.published ? '$t:sirsoft-page.admin.page.published_status.published' : '$t:...'}}표현식 결과가 번역되지 않고$t:key문자열 그대로 노출 - 원인:
renderItemChildren.resolveValue에서 표현식 평가 후 결과가$t:접두사 문자열인 경우translationEngine.resolveTranslations()호출이 없었음 (DynamicRenderer의resolveTranslationsDeep와 달리) - 수정: 단일 바인딩 표현식 및 문자열 보간 결과에
$t:패턴 감지 시 번역 처리 추가 (raw:바인딩은 면제)
- 증상:
preprocessTranslationTokens정규식에서 하이픈(-) 포함 모듈키 미인식 수정 (DataBindingEngine)- 증상:
$t:sirsoft-page.admin.key패턴에서sirsoft이후-page가 키의 일부로 인식되지 않음 - 원인: 문자 클래스
[a-zA-Z0-9_.]에 하이픈 누락 - 수정: Step 1(따옴표 내), Step 2(따옴표 외),
preprocessOptionalChaining$t: 토큰 정규식 3곳에\-추가
- 증상:
[engine-v1.28.0]
Added
_changedKeys디바운스 병합 프로토콜 — 객체 값 이벤트의 stale closure 키 유실 방지 (ActionDispatcher)- 디바운스 대기 중 동일 debounceKey로 들어오는 객체 값의 변경 키만 누적 병합
debounceAccumulatedValuesMap으로 누적, 타이머 fire 시 자동 정리_changedKeys미포함 이벤트는 기존 동작 유지 (하위 호환)extractEventData에서 커스텀 이벤트의_changedKeys메타데이터 보존
Fixed
- 커스텀 컴포넌트 이벤트(plain object) → synthetic event 변환 시
_changedKeys메타데이터 누락 수정 (ActionDispatcher)isCustomComponentEvent분기에서 synthetic event 생성 시_changedKeys를 복사하지 않아, MultilingualInput의_changedKeys프로토콜이 실제 환경에서 동작하지 않았음- DOM 이벤트(
preventDefault포함) 경로에서는 정상이었으나, React 컴포넌트의 plain object 이벤트 경로에서 유실
[engine-v1.27.0]
Fixed
_localInit처리 전 자식 컴포넌트 usgeEffect의 setState가 API 데이터를 stale 기본값으로 덮어쓰는 레이스 컨디션 수정 (DynamicRenderer)- 증상: 직접 URL 접근 시 상품 수정 저장 → 422 검증 오류 (category_ids, options 필수 위반). SPA 네비게이션에서는 정상
- 원인: (1)
_localInit(useEffect)이 실행되기 전 자식 useEffect(FileUploaderonFilesChange)가 먼저 발동 → ActionDispatcher가 stalecontext.state(init_actions 빈 배열) 기반__g7PendingLocalState생성 (2)handleLocalSetState에서__g7SetLocalOverrideKeys(init_actions가 설정한 stale 값)가__g7PendingLocalState보다 우선 →effectivePrev에 stale 빈 배열 적용 - 수정:
_localInit데이터가 준비되면 useLayoutEffect(모든 useEffect보다 먼저 실행)에서__g7PendingLocalState와__7SetLocalOverrideKeys를 API 데이터로 사전 동기화 — 후속 자식 useEffect의 ActionDispatcher가 정확한 base state 참조
Added
{{raw:expression}}바인딩 문법 — 번역 면제 마커 시스템 (DataBindingEngine, DynamicRenderer, RenderHelpers)raw:접두사가 붙은 바인딩 결과는resolveTranslationsDeep에서 번역을 건너뜀- 사용자 입력 데이터(게시판 제목, 댓글 등)에
$t:패턴이 포함되어도 원본 보존 - Unicode Noncharacter (
\uFDD0,\uFDD1) 기반 내부 마커 — 사용자 입력과 충돌 불가 - 파이프, 복잡한 표현식, 객체/배열 결과, 혼합 보간 모두 지원
rawMarkers.ts공유 유틸리티 모듈 —wrapRaw,unwrapRaw,wrapRawDeep,isRawWrapped,containsRawMarker
[engine-v1.26.1]
Added
- 프리뷰 모드 지원:
PREVIEW_SUPPRESSED_HANDLERS상수 기반 핸들러 억제 — navigate, navigateBack, navigateForward, replaceUrl, refresh, logout (ActionDispatcher)
Fixed
- JSON 구조 문자열 내부의
$t:번역 토큰이resolveTranslationsDeep에 의해 번역되어 원본 데이터가 손상되는 버그 수정 (DynamicRenderer)- 증상: CodeEditor에서 레이아웃 JSON 편집 후 프리뷰 시
$t:토큰이 사라져 다국어가 깨짐 - 원인:
resolveTranslationsDeep가 모든 string prop에 적용되어, JSON 문자열 내부의$t:토큰까지 번역 - 수정:
{/[로 시작하고}/]로 끝나는 JSON 구조 문자열은 번역 건너뛰기
- 증상: CodeEditor에서 레이아웃 JSON 편집 후 프리뷰 시
- 프리뷰 모드 레이아웃 기능 억제:
PREVIEW_SUPPRESSED_LAYOUT_FEATURES상수 — redirect 등 페이지 이탈 유발 레이아웃 기능 정의 (ActionDispatcher) ActionDispatcher.setPreviewMode()/isPreviewMode()API 추가 — 프리뷰 모드 활성화/비활성화 및 조회ActionDispatcher.getPreviewSuppressedHandlers()/getPreviewSuppressedLayoutFeatures()static 메서드 — 억제 대상 목록 외부 조회용_global.__isPreview전역 상태 플래그 추가 — 프리뷰 모드 시 레이아웃 JSON에서 조건부 렌더링에 사용 가능 (TemplateApp)- 프리뷰 안내 배너: 프리뷰 모드 시 최상단에 고정 배너 표시 — 페이지 이동 비활성화 안내 (SystemBannerManager)
SystemBannerManager신규 모듈: 범용 시스템 배너 관리자 — show/hide/hideAll API, 다국어 메시지, order 기반 정렬, #app paddingTop 자동 조정- 프리뷰 모드에서
blur_until_loaded비활성화 — 시각적 확인이 목적이므로 데이터소스 로딩 상태와 무관하게 블러 미적용 (DynamicRenderer)
[engine-v1.26.0]
Added
- 시스템 레이아웃 분기 지원:
__preview__예약 레이아웃 이름을 감지하여 별도 API 엔드포인트(/api/layouts/preview/{token}.json)로 라우팅 (LayoutLoader, TemplateApp)- LayoutLoader:
fetchLayout()에서__preview__/접두사 감지 시 프리뷰 전용 API URL 구성 - TemplateApp:
__preview__레이아웃 감지 시 route params에서 token을 추출하여 layoutPath에 포함
- LayoutLoader:
[engine-v1.25.1]
Fixed
- blur_until_loaded 래퍼가 CSS Grid 레이아웃을 깨뜨리는 버그 수정 (DynamicRenderer)
- blur_until_loaded가 그리드 아이템을 래퍼 div로 감싸면서 col-span-, row-span- 등 그리드 클래스가 그리드 컨테이너의 직접 자식이 아니게 되어 적용되지 않음
- 그리드 아이템 관련 클래스(col-span-, row-span-, self-, order- 등)를 blur 래퍼로 자동 전달
- 반응형 접두사(sm:, md:, lg:, xl:, 2xl:) 포함 패턴 지원
[engine-v1.25.0]
Added
- $t: 파라미터 값 내 중첩 번역 토큰 지원 (TranslationEngine)
$t:key1|status=$t:key2형태에서 파라미터 값 위치의$t:key2를 메인 해석 전에 사전 번역- 다중 중첩 대응: 해석 결과에 다시
=$t:가 포함되면 변화 없을 때까지 반복 (깊이 제한 5) =$t:패턴이 없으면 사전 해석 단계 자체를 skip (기존 동작 무영향)
[engine-v1.24.8]
Fixed
- extends 기반 레이아웃에서 SPA 네비게이션 시 base 컴포넌트(사이드바, 헤더, 로고 등) 불필요 remount로 깜빡이는 버그 수정 (template-engine, DynamicRenderer, LayoutService)
- engine-v1.24.5의 layout_name key 추가가 모든 최상위 컴포넌트에 적용되어 base 컴포넌트까지 강제 remount
- LayoutService.replaceSlots()에서 base 컴포넌트에
_fromBase: true자동 마킹 _fromBase컴포넌트는 stable key(componentDef.id) 사용 → 페이지 전환 시 보존(update)- 슬롯 래퍼(slot 매칭 컴포넌트)는
_fromBase미마킹 → remount 보장 (localDynamicState 초기화) - 슬롯 children(페이지 고유 컴포넌트)은
_fromBase미마킹 → 기존 layout_name key → remount 보장 - non-extends 레이아웃은 replaceSlots 미호출 →
_fromBase없음 → 기존 동작 100% 유지
_fromBase보존 컴포넌트의 localDynamicState가 SPA 네비게이션 시 초기화되지 않아 이전 페이지 상태(visibleFilters 등)가 하위 트리에 전파되는 회귀 수정 (DynamicRenderer)- 원인: stable key → React가 컴포넌트 보존 → localDynamicState 잔존 → componentContext.state를 통해 모든 children에 cascading 전파
- 증상: 주문목록 → 주문상세 이동 시 DataGrid에 컬럼/데이터 미표시 (이전 페이지의 visibleFilters 오염)
- 수정: layoutKey(= layout_name) 변경 감지 시
_fromBase컴포넌트의 localDynamicState를{ loadingActions: {} }로 초기화 - sidebar 메뉴 open/close 등 React 컴포넌트 내부 useState는 localDynamicState와 독립 → 보존됨
[engine-v1.24.7]
Fixed
- 모달 내부 setLocal() 호출 시 모달 localDynamicState가 globalState._local에 오염되어 페이지 DataGrid 깨지는 버그 수정 (G7CoreGlobals)
- engine-v1.22.1에서 도입된 actionContext.state 병합이 모달 컨텍스트에서도 적용되어 모달 전용 상태(cancelItems, refundLoading 등)가 페이지 _local에 유입
- __g7LayoutContextStack 기반 모달 감지: 모달 내부에서는 actionContext.state 병합 제외, 페이지에서는 기존 동작 유지
- init_actions에서 conditions 핸들러 사용 시 conditions 배열이 전달되지 않아 조건 분기가 무시되던 버그 수정 (TemplateApp, LayoutLoader)
- executeInitActions에서 actionDef 구성 시 conditions 프로퍼티 누락 → handleConditions에서 undefined로 평가 → 경고만 출력하고 리턴
- InitActionDefinition 타입에 conditions 프로퍼티 추가, actionDef에 conditions 전달 추가
[engine-v1.24.6]
Fixed
- 주문 취소 모달 닫기 후 주문상세 DataGrid 깨지는 버그 수정 — _local 상태 교차 오염 방지 (TemplateApp, ActionDispatcher)
- handleRouteChange에서
__g7LastSetLocalSnapshot,__g7SetLocalOverrideKeys,__g7SequenceLocalSync3개 전역 변수 미정리 → 이전 페이지 상태 잔존하여 다음 페이지 _local에 축적 - handleSetState isRealComponentContext 경로에서 deep 모드 시 전체 _local 스냅샷을 context.setState에 전달 → localDynamicState에 전체 상태 축적 → dataContext._local override → SPA 이동 후 stale 필드 잔존
- 수정: context.setState에는 변경 필드만 전달, __g7PendingLocalState에만 전체 병합 상태 유지
- handleRouteChange에서
[engine-v1.24.5]
Fixed
- SPA 네비게이션 시 DynamicRenderer 내부 useState가 이전 페이지 _local 잔존하여 DataGrid 데이터 깨지는 버그 수정 (template-engine)
- 동일 base layout 공유 페이지 간 루트 컴포넌트 ID 동일 → engine-v1.24.4 트리 구조 통일 후 React가 컴포넌트 보존(remount 안 함)
- DynamicRenderer key에 layout_name 포함하여 레이아웃 변경 시 React 강제 remount
- renderTemplate(), updateTemplateData() 양쪽 모두 적용
- layout_name 부재 시 기존 동작(componentDef.id만 사용) 유지 — 하위 호환
[engine-v1.24.4]
Fixed
- renderTemplate()/updateTemplateData() React 트리 구조 통일 — 이중 렌더링(깜빡임) 해소 (template-engine, DynamicRenderer)
renderTemplate()은ResponsiveProvider → SlotProvider → [children]구조로,updateTemplateData()는ResponsiveProvider → [children]구조로 달랐음- 데이터소스 완료 시
updateTemplateData()호출 → React가 트리 구조 변경 감지 → 전체 서브트리 언마운트/리마운트 updateTemplateData()에 외부 SlotProvider 추가하여 양쪽 트리 구조 통일DynamicRenderer의isRootRendererSlotProvider 래핑 제거 (외부에서 제공하므로 이중 래핑 방지)
- SlotProvider
window.__slotContextValue전역 변수 설정을useEffect→useLayoutEffect로 변경 (SlotContext)- 이중 렌더링 해소 후 슬롯 등록 타이밍 이슈 발생: 자식
useEffect(슬롯 등록)가 부모useEffect(전역 변수 설정)보다 먼저 실행 useLayoutEffect는 모든useEffect보다 먼저 실행되므로 전역 변수가 슬롯 등록 시점에 항상 사용 가능
- 이중 렌더링 해소 후 슬롯 등록 타이밍 이슈 발생: 자식
[engine-v1.24.3]
Fixed
- 다국어 파이프 파라미터에서 산술 연산자(+, -, *, /, %) 포함 표현식이 단순 경로로 오인되어 빈 문자열 반환되던 버그 수정 (TranslationEngine)
isComplexExpression정규식에 산술/비교 연산자 및 공백 패턴 추가- 영향 예시:
$t:key|count={{row.options_count - 1}}→ 기존에 count 빈 문자열, 수정 후 정상 평가
[engine-v1.24.2]
Added
transition_overlay.fallback_target— 3단계 스켈레톤 타겟팅 지원 (TemplateApp)- target DOM 존재 → 해당 영역만 스켈레톤 (페이지 내부 전환, 예: 마이페이지 탭)
- target 미존재 + fallback_target 존재 → 해당 영역만 스켈레톤 (페이지 전환)
- 둘 다 미존재 → 전체 페이지 스켈레톤 (초기 로드,
position:fixed; inset:0) - CSS
::after가림막도 scope에 따라 적절한 selector 적용 - fullpage scope 시 전체 컴포넌트 트리를 스켈레톤으로 렌더
LayoutLoader.ts타입 정의 +UpdateLayoutContentRequest검증 규칙 추가
[engine-v1.24.1]
Fixed
- 초기 페이지 로드 시 스켈레톤 미표시 — target DOM(
#main_content_area등)이renderTemplate()전에 존재하지 않아 opaque fallback 후 시각적 효과 없음 (TemplateApp)- target DOM 부재 시
#app을 fallback 타겟으로 사용하여 스켈레톤 렌더 - CSS
::after주입도#appselector로 적용 - 페이지 전환 시에는 기존대로 지정된 target 사용
- target DOM 부재 시
[engine-v1.24.0]
Added
transition_overlayskeleton 스타일 — 레이아웃 JSON 컴포넌트 트리 기반 동적 스켈레톤 UI 렌더링 (TemplateApp)style: "skeleton"+skeleton.component: 레이아웃에서 단일 스켈레톤 렌더러 컴포넌트 지정- 엔진이
components트리 +options(animation, iteration_count)를 props로 전달 - 컴포넌트가 트리를 재귀 순회하여 스켈레톤 플레이스홀더 자동 생성
- 데이터 로드 완료 후
renderTemplate()호출 시 React reconciliation으로 자동 교체 - 컴포넌트 미등록/target 미존재 시 opaque 스타일 자동 폴백
skeleton.animation: pulse / wave / none 선택skeleton.iteration_count: iteration 블록 기본 반복 횟수- 백엔드
UpdateLayoutContentRequest검증 규칙 동기화
[engine-v1.23.0]
Added
transition_overlay레이아웃 옵션 — 페이지 전환 시 오버레이로 stale flash 방지 (TemplateApp)true(축약): opaque 스타일 (document.body에 fixed div 폴백){ enabled, style, target }: opaque / blur / fade 선택 + 타겟 컨테이너 지정 가능target지정 시: CSS<style>태그를<head>에 주입 →::after의사 요소로 해당 영역만 덮음 (React 렌더 트리 외부, 형제 요소 미영향)target미지정 시:document.body에position:fixeddiv 삽입 (폴백)- React 렌더 사이클과 독립적 — 동기 DOM/CSS 조작으로 즉시 적용
- 모든 경로(progressive/non-progressive/취소/에러)에서 오버레이 정리 보장
[engine-v1.22.1]
Fixed
- setLocal() 후 openModal 시 dynamicState 값 누락 — actionContext.state를 globalLocal과 deepMerge하여 __g7PendingLocalState에 전체 _local 반영 (G7CoreGlobals)
- setState(target: "_local")로 설정된 값은 localDynamicState에만 존재하고 globalLocal에 없음
- setLocal → openModal 시퀀스에서 $parent._local 스냅샷에 dynamicState 값이 포함되지 않는 문제 수정
[engine-v1.22.0]
Added
- DevTools 모달 정의 교차 검증 — modalStack에 열린 모달 ID가 레이아웃 modals 섹션에 미정의 시
missing-definition이슈 기록 (G7DevToolsCore)
[engine-v1.21.2]
Fixed
- getLocal() await 후 stale 반환 근본 수정 —
__g7LastSetLocalSnapshotfallback 도입 (G7CoreGlobals)__g7PendingLocalState가 useLayoutEffect에서 클리어된 후에도 최신 setLocal 값 반환dataContext._local갱신 시점에 queueMicrotask로 자동 클리어 (DynamicRenderer)handleLocalSetState에서는 참조하지 않으므로 기존 stale 오염 방지 로직과 충돌 없음
[engine-v1.21.1]
Added
condition속성을if의 별칭으로 네이티브 지원 — 컴포넌트 정의, renderItemChildren, renderWithIteration 모든 경로에서 동작 (RenderHelpers, DynamicRenderer)- evaluateIfCondition에서 boolean 타입 직접 처리 — 엔진 prop 사전 해석으로 조건이 boolean이 된 경우 대응 (RenderHelpers)
- ComponentDefinition 인터페이스에
condition?: string | boolean속성 추가 (DynamicRenderer)
[engine-v1.21.0]
Added
- suppress 에러 핸들러 — 에러 전파를 의도적으로 방지하는 no-op 핸들러 (ActionDispatcher)
- multipart/form-data contentType 자동 감지 및 FormData 변환 (DataSourceManager)
- errorCondition 기능 — API 200 응답이어도 조건부 에러 처리 (DataSourceManager)
- replaceUrl 핸들러 — refetch 없이 URL만 변경
Fixed
- SPA 네비게이션 시 _global._local에 이전 페이지 _local 상태 잔존 — 스냅샷 순서 수정 (TemplateApp)
- Form 자동 바인딩 setState 경합 — pendingLocal || context.state 우선 사용 (ActionDispatcher)
- Form 자동 바인딩 bindingType 메타데이터 기반 boolean 바인딩 수정 (DynamicRenderer)
- onChange raw value fallback 과도 적용 회귀 제거 (ActionDispatcher)
- DataSourceManager isMultipart 변수 TDZ(Temporal Dead Zone) — 선언 순서 수정
- errorHandling 레이아웃 병합 누락 및 showErrorPage 안정성 개선
- blocking 데이터소스 + errorHandling 데드락 — fallback 동기 적용, 에러핸들러 비동기 실행
- DataSourceManager fallback/errorHandling 우선순위 — errorHandling 먼저, fallback 후적용
- resolveObject 복잡 표현식 미지원 수정 (DataBindingEngine)
- dispatch 경로 context.data._local stale 버그 수정 (G7CoreGlobals)
- refetchDataSource stale localState 버그 수정 (ActionDispatcher)
- DynamicRenderer _localInit shallow merge — deep merge 적용
- 복수 root DynamicRenderer 전역 플래그 경쟁 조건 수정 (__g7ForcedLocalFields 등)
- sequence 내 커스텀 핸들러 상태 동기화 수정 (ActionDispatcher, G7CoreGlobals)
- setState params 키에 {{}} 표현식 사용 시 경고 출력
[engine-v1.20.0]
Added
- debounceFlush 기능 - 대기 중인 디바운스 핸들러 즉시 실행
Fixed
- 디바운스 액션 레이스 컨디션 — 연속 호출 시 이전 결과 누락 방지 (flush 메커니즘)
[engine-v1.19.1]
Fixed
- DataGrid footerCells/footerCardChildren 내부 iteration 패턴 미작동 — 3가지 근본 원인 수정 (DataBindingEngine, DynamicRenderer, RenderHelpers)
resolveObject가 iteration이 있는 ComponentDefinition을 사전 해석하여 iteration 변수(currency등)가 미존재 상태에서 에러 발생 → iteration 객체 감지 시 선평가 건너뛰기componentContext에 데이터소스(order,active_carriers등)가 포함되지 않아renderItemChildren컨텍스트에서 데이터소스 표현식이 빈 값으로 평가 →parentDataContext필드 추가getEffectiveContext가componentContext.state(=_local)만 병합하고 데이터소스 키 미병합 →parentDataContext키를 최상위 컨텍스트에 병합 (기존 키 우선)
[engine-v1.19.0]
Added
- actionRef - named_actions 참조 시스템
- named_actions - 명명된 액션 정의 및 재사용
- sequence 내 setState 후 refetchDataSource 호출 시 마이크로태스크 기반 배칭
[engine-v1.18.0]
Added
- DataSource onSuccess 콜백에서 response 객체로 API 응답 데이터 접근
- ErrorHandling 에러 핸들링 시스템
Fixed
- (engine-v1.18.1) __g7SetLocalOverrideKeys 처리를 별도 useLayoutEffect([dataContext._local])로 이동
- (engine-v1.18.2) __g7ForcedLocalFields 조건부 클리어 (불필요한 클리어 방지)
- (engine-v1.18.3) 전역 플래그 클리어를 queueMicrotask로 지연 처리
[engine-v1.17.0]
Fixed
상태 동기화 (setLocal/dispatch)
- 커스텀 핸들러에서 setLocal 후 dispatch 호출 시 최신 로컬 상태 참조
- setLocal 후 즉시 dispatch 호출 시 최신 로컬 상태 참조 지원 (G7CoreGlobals)
- (engine-v1.17.1) 커스텀 핸들러에서 G7Core.state.getLocal() 호출 시 최신 상태 참조 지원
- (engine-v1.17.2) _isDispatchFallbackContext가 true면 전역 폴백(setGlobalState) 처리
- (engine-v1.17.2) sequence 내 연속 setState 시 이전 setState 결과 참조 지원
- (engine-v1.17.3) componentContext + context.data 저장하여 $parent 바인딩 지원
- (engine-v1.17.4) 비동기 콜백에서 setLocal 호출 시 dynamicState stale 값 방지
- (engine-v1.17.4) __g7ForcedLocalFields 처리로 최신 필드 값 우선 적용
- (engine-v1.17.5) dataKey 자동 바인딩 컴포넌트에서 setState 핸들러 호출 시 stale 값 방지
- (engine-v1.17.6) globalLocal + pendingState 2단계 병합
- (engine-v1.17.7) globalLocal 사용 (actionContext.state 사용 안 함)
- (engine-v1.17.7) actionContext 유무와 관계없이 항상 globalLocal 업데이트
- (engine-v1.17.8) isRootRenderer=false일 때 클리어 안 되는 버그 수정
- (engine-v1.17.8) setLocal이 업데이트한 키를 기록하여 ROOT의 localDynamicState에서 제거
- (engine-v1.17.9) resolvedPayload(변경된 필드만) 사용 — finalPayload(전체 상태 스냅샷) 사용 방지
- (engine-v1.17.10) pendingLocal 우선 사용 (Form 자동 바인딩 경합 방지)
- (engine-v1.17.10) __g7PendingLocalState 클리어
- (engine-v1.17.11) 모든 루트급 컴포넌트에서 __g7ForcedLocalFields, __g7PendingLocalState 클리어
- (engine-v1.17.12) 전역 _local 상태도 업데이트 (setLocal과 동일)
setState 옵션 및 동작
- setState 얕은 병합이 중첩 객체 덮어쓰기 — merge: "deep" / "replace" 옵션 추가 (ActionDispatcher)
- (engine-v1.17.1) setState 배열 조작 미지원 — arrayMethod 파라미터 추가 (push, filter, splice, map)
- (engine-v1.17.2) setState 동일 값 불필요 리렌더 방지 — shallow equality 검사 (G7CoreGlobals)
- (engine-v1.17.3) setState 동적 키 경로 표현식 미평가 — params key 표현식 평가 추가 (ActionDispatcher)
- (engine-v1.17.3) setState onSuccess 컨텍스트에서 {{response.xxx}} 표현식 미평가 수정
- (engine-v1.17.5) setState
_local+_global동시 수정 시 배치 업데이트 (sequence 내) - (engine-v1.17.6) setState 순환 업데이트 감지 — 깊이 10 초과 시 에러 (G7CoreGlobals)
- (engine-v1.17.7) setState _isolated 스코프 지원 (target: "isolated")
- (engine-v1.17.9) setState undefined 값 처리 — no-op (removeKey 별도 액션으로 명시적 삭제)
- (engine-v1.17.9) setState 깊은 중첩 경로(4+ 레벨) 중간 객체/배열 자동 생성 (ActionDispatcher)
- (engine-v1.17.11) setState _local 전역 핸들러에서 target 컴포넌트 ID 스코프 지원
- setState dot notation — 멀티 키 병합 시 이전 키 변경 유실 방지 (ActionDispatcher)
initGlobal / initLocal
- initGlobal SPA 네비게이션 시 이미 초기화된 키 덮어쓰기 방지 (TemplateApp)
- (engine-v1.17.1) initGlobal 데이터소스 로드 전 실행 — DS 참조 표현식 지연 평가
- (engine-v1.17.2) initGlobal 배열 deep merge 파괴 방지 — replace 전략 적용
- (engine-v1.17.2) initGlobal 조건 표현식 (route.id ? 'edit' : 'create') 평가 지원
- (engine-v1.17.3) initGlobal dot 경로("settings.display.mode") 중첩 객체 확장
- (engine-v1.17.3) initLocal partial/extends 레이아웃에서 무시되는 문제 수정 (DynamicRenderer)
- (engine-v1.17.4) initGlobal 레이아웃 변경 시 이전 키 클린업 (TemplateApp)
- (engine-v1.17.4) initLocal Form 자동 바인딩 우선순위 충돌 해결 — initLocal 후 적용
init_actions 타이밍
- init_actions 컴포넌트 마운트 전 실행 — 첫 렌더 사이클 후 지연 (TemplateApp)
- (engine-v1.17.1) init_actions 조건 토글 시 재실행 — 컴포넌트 ID 기반 run-once 가드 (DynamicRenderer)
- (engine-v1.17.1) init_actions replaceUrl가 history.pushState 사용 — replaceState로 수정
- init_actions sequence 내 apiCall 비동기 대기 미지원 수정 (ActionDispatcher)
- (engine-v1.17.2) init_actions route.id 등 라우트 파라미터 선처리 후 실행 (TemplateApp)
- (engine-v1.17.3) init_actions 모달 내부 실행 미지원 — 모달 마운트 시 처리 추가 (DynamicRenderer)
- (engine-v1.17.4) init_actions HMR 중복 실행 방지 (TemplateApp)
- (engine-v1.17.5) init_actions 부모-자식 실행 순서 미보장 — 부모 완료 후 자식 마운트 (DynamicRenderer)
- (engine-v1.17.7) init_actions blocking 데이터소스 완료 대기 후 실행 (TemplateApp)
- (engine-v1.17.8) init_actions setState
_global첫 렌더 전 동기 초기화 경로 (G7CoreGlobals) - (engine-v1.17.9) init_actions onSuccess 중첩 비동기 액션 대기 수정 (ActionDispatcher)
dataKey / Form 자동 바인딩
- (engine-v1.17.3) dataKey 자동 바인딩이 명시적 setState 덮어쓰기 — __g7ForcedLocalFields 추적 (FormContext)
- (engine-v1.17.3) Form 자동 바인딩 명시적 value prop 감지 시 스킵 (DynamicRenderer)
- (engine-v1.17.4) dataKey 중첩 객체 경로 (dot-notation name) 생성 지원 (FormContext)
- (engine-v1.17.4) Form 자동 바인딩 sortable 내 컨텍스트 차단 — parentFormContextProp={undefined} 지원
- (engine-v1.17.5) Form 제출 이중 클릭 방지 — 로딩 상태 기반 차단 (ActionDispatcher)
- (engine-v1.17.6) dataKey 데이터소스 로드 전 빈 구조 생성 방지 — waitForData 대기 (DynamicRenderer)
- (engine-v1.17.7) 동일 페이지 복수 폼 dataKey 스코프 분리 — 폼 ID 기반 컨텍스트 (FormContext)
- (engine-v1.17.8) dataKey iteration 내 인덱스 스코프 적용 (FormContext, DynamicRenderer)
- (engine-v1.17.8) FileUploader File/Blob 타입 감지 — 자동 직렬화 제외 (FormContext)
Stale Closure
- (engine-v1.17.5) 비동기 콜백(onSuccess/onError)에서 캡처된 상태 대신 현재 상태 재조회 (ActionDispatcher)
- (engine-v1.17.6) componentContext ref 기반 접근으로 마운트 시점 캡처 방지 (DynamicRenderer)
- (engine-v1.17.6) useCallback 과도한 메모이제이션 제거 — 인라인 함수 전환 (DynamicRenderer)
- (engine-v1.17.6) 이벤트 핸들러 라이브 상태 조회 래핑 (DynamicRenderer)
- (engine-v1.17.7) computed 콜백 스냅샷 대신 라이브 상태 getter 사용 (G7CoreGlobals)
- (engine-v1.17.8) _global setState 레이스 컨디션 — 함수형 업데이터 병합 패턴 (G7CoreGlobals)
- (engine-v1.17.8) _global subscribe 콜백 ref 기반 등록 (G7CoreGlobals)
- (engine-v1.17.9) useControllableState prop 동기화 효과 추가 (useControllableState)
캐시
- (engine-v1.17.1) iteration 표현식 캐시 — 첫 아이템 값만 표시되는 문제 (skipCache 적용, DynamicRenderer)
- (engine-v1.17.2) 상태 의존 경로(_local/_global) 30초 영구 캐시 stale — skipCache 적용 (DataBindingEngine)
- (engine-v1.17.2) if 조건 표현식 캐시 — 상태 변경 후 조건 미토글 (skipCache 적용, DynamicRenderer)
- (engine-v1.17.3) 렌더 캐시 컴포넌트 인스턴스 ID 스코프 분리 (DynamicRenderer)
- (engine-v1.17.4) SPA 네비게이션 시 이전 페이지 캐시 잔존 — 레이아웃 변경 시 전체 캐시 클리어
- (engine-v1.17.4) 데이터소스 캐시 키에 직렬화된 params 해시 포함 (DataSourceManager)
- (engine-v1.17.5) computed 의존성 추적 기반 캐시 무효화 (DataBindingEngine)
- (engine-v1.17.5) 모달 데이터소스 refreshOnOpen 시 캐시 클리어 (ModalDataSourceWrapper)
- (engine-v1.17.6) apiCall 기본 cache: false 설정 — 명시적 opt-in 방식 (ActionDispatcher)
- (engine-v1.17.7) subscribe 알림 캐시 우회 — 라이브 상태 직접 조회 (G7CoreGlobals)
- (engine-v1.17.8) 데이터소스 refreshOn 트리거 시 캐시 버스트 (DataSourceManager)
- (engine-v1.17.9) 프리컴파일 표현식 캐시 키에 컨텍스트 변수명 포함 (RenderHelpers)
- (engine-v1.17.11) _computed prop 캐시 버그 — getComputedAwareOptions() skipCache 적용 (DynamicRenderer)
기타
- (engine-v1.17.1) Form validation 에러 응답 경로 정규화 — error.errors / error.message 통일 (ActionDispatcher)
- (engine-v1.17.2) Button이 Form 내에서 기본 type="submit" — type="button" 기본값 적용 (ComponentRegistry)
- (engine-v1.17.5) iteration item_var 스코프 격리 — 부모 루프 변수 자식에 누출 방지 (DynamicRenderer)
- (engine-v1.17.6) 데이터소스 refreshOn 무한루프 — 리프레시 순환 감지 및 디바운스 (ActionDispatcher)
- (engine-v1.17.6) cellChildren 부모 데이터 변경 시 stale props — 재평가 추가 (DynamicRenderer)
- (engine-v1.17.6) 커스텀 핸들러 등록 타이밍 — 등록 대기 큐, 지연 액션 대기 (ActionDispatcher)
- (engine-v1.17.6) 커스텀 핸들러 async 반환값 sequence 내 자동 await (ActionDispatcher)
- (engine-v1.17.7) computed 순환 의존성 감지 — visited 세트 기반 에러 발생 (DataBindingEngine)
- (engine-v1.17.7) blocking 데이터소스 에러 시 무한 로딩 — fallback/에러 핸들러 트리거
- (engine-v1.17.8) 언마운트된 컴포넌트 setLocal 무시 — no-op 처리 (G7CoreGlobals)
- (engine-v1.17.8) 데이터소스 의존성 체이닝 — dependsOn 실행 순서 보장 (ActionDispatcher)
- (engine-v1.17.9) 데이터소스 params 표현식 캐시 — 상태 변경 후 re-fetch 미실행 방지 (G7CoreGlobals)
- (engine-v1.17.10) __g7PendingLocalState 컴포넌트 마운트 시 flush (DynamicRenderer)
- (engine-v1.17.10) __g7SetLocalOverrideKeys 레이아웃 변경 시 클리어 (G7CoreGlobals)
- (engine-v1.17.10) Input controlled/uncontrolled 전환 방지 — 빈 문자열 fallback
- (engine-v1.17.11) errorHandling + blocking 데드락 — fallback 정의 시 에러 핸들러 비동기 실행
[engine-v1.16.0]
Added
- globalHeaders - API 호출 시 패턴 매칭 기반 전역 HTTP 헤더 추가
- setGlobalHeaders, matchesPattern, getMatchingGlobalHeaders 메서드 (ActionDispatcher)
- DataSource globalHeaders - API 데이터 소스 헤더 추가
- parentDataContext - {{$parent._local.xxx}} 바인딩 지원 (DynamicRenderer)
- getParent, setParentLocal, setParentGlobal - 부모 컨텍스트 API (G7CoreGlobals)
- handleParentScopeSetState - $parent._global/._local setState 처리
- 레이아웃 레벨 globalHeaders 설정 (LayoutLoader)
- $parent 바인딩 컨텍스트 (모달 및 중첩 레이아웃)
Fixed
- DataSourceManager onSuccess에서 openModal 시 $parent._local 접근 불가 수정
- ActionDispatcher 복합 표현식 (삼항 연산자 등) 미평가 수정
- setLocal()이 expandedRows 등 배열 상태를 초기값으로 덮어쓰기 수정 (DynamicRenderer)
- _computed stale closure 및 deepMerge sparse array 수정 (ActionDispatcher, DynamicRenderer, G7CoreGlobals)
- setParentLocal 후 getLocal() stale 데이터 반환 수정 (DynamicRenderer, G7CoreGlobals)
- deepMergeState 배열→객체 잘못된 병합 수정
- 콜백 prop 내 액션 정의 선평가 방지 및 비동기 콜백 상태 참조 수정 (DataBindingEngine, G7CoreGlobals)
- 레이아웃 전환 시 _global._local cleanup 미실행 수정 (TemplateApp)
[engine-v1.15.0]
Added
- permissions - 레이아웃 접근 권한 식별자 배열 (401/403 응답)
- extensionPointProps - 확장 영역에서 전달 가능한 props
- isInsideIteration - iteration 캐시 버그 방지 플래그
Fixed
- expandChildren _computed 상태 동기화 — computedRef 패턴 적용 (DynamicRenderer, G7CoreGlobals)
_local변경 시_computed재계산 미발생 수정 (TemplateApp, DynamicRenderer)- expandChildren 상태 동기화 버그 — stateRef 패턴 적용 (DynamicRenderer, G7CoreGlobals)
- cellChildren 글로벌 상태 접근 불가 수정 (G7CoreGlobals)
- $computed 별칭 미지원 수정 (DataBindingEngine)
- 언어 전환 시 모듈 핸들러 재등록 누락 수정
[engine-v1.14.0]
Added
- sortable - @dnd-kit 기반 네이티브 드래그앤드롭 정렬 기능
- SortableContainer, SortableItemWrapper 컴포넌트
- itemTemplate - sortable 내 아이템 렌더링 템플릿
- _isolated - 격리된 상태 바인딩 (성능 최적화)
- isolatedStateId - 격리된 상태 식별자 (DevTools용)
- IsolatedStateContext 시스템
Fixed
- DataGrid expandChildren 상태 동기화 버그 수정 (DynamicRenderer)
- $event 표현식 캐시로 조건 잘못 평가 수정 (ActionDispatcher)
- _local 경로 캐시로 상태 변경 미반영 수정 (DataBindingEngine)
- DynamicRenderer text prop 파이프 표현식 미처리 수정
- 언어 전환 시 템플릿 핸들러 재등록 누락 수정 (TemplateApp)
[engine-v1.13.0]
Added
- classMap - 조건부 CSS 클래스 매핑 (중첩 삼항 연산자 대체)
- componentPath - 경로 기반 컴포넌트 식별 (ID 없는 컴포넌트 편집용)
- WYSIWYG 에디터 지원 (ensureComponentId, moveComponentByPaths, updateComponentByPath)
Fixed
- onSuccess 배열 내 복수 setState 순차 실행 시 상태 동기화 수정 (ActionDispatcher)
- DataBindingEngine $t: 토큰 처리 개선
- init_actions setGlobalState 비동기 에러 수정 (TemplateApp)
- Form 자동 바인딩 debounce stale 상태 수정 (DynamicRenderer, FormContext, G7CoreGlobals)
- cellChildren remount 리렌더 및 _remountKeys 경로 수정 (ActionDispatcher, DynamicRenderer)
- remount 핸들러 cellChildren ID 표현식 해석 버그 수정 (G7CoreGlobals, RenderHelpers)
- sequence 내 closeModal 후 setState 시 모달 재오픈 수정 (ActionDispatcher)
- setState dot notation 멀티 키 병합 시 이전 키 변경 유실 수정 (ActionDispatcher)
[engine-v1.12.0]
Added
- expandChildren - 확장 영역 내 액션에서 부모 상태 업데이트
- _isolated 컨텍스트 시스템 (IsolatedStateContext)
- onDragStart, onDragEnd - 편집기에서 컴포넌트 드래그 지원
Fixed
- actions 객체 형식 호환성 에러 수정 (DynamicRenderer)
- LayoutLoader 401 에러 시 토큰 삭제 및 재시도 미동작 수정
- SPA navigate 시 DataGrid 빈 화면 렌더링 수정 (TemplateApp)
- setState _global dot notation 시 기존 상태 유실 수정 (ActionDispatcher)
- 커스텀 컴포넌트 change 이벤트 핸들링 버그 수정 (ActionDispatcher)
- style prop CSS 문자열 자동 객체 변환 미지원 수정 (DynamicRenderer)
[engine-v1.11.0]
Added
- onComponentEvent - 컴포넌트 이벤트 구독 및 핸들러 실행
- extensionPointProps - 확장 영역 props 전달
- isEditMode, onComponentSelect, onComponentHover - 편집기 모드 지원
- initActions - 동적 표현식 기반 초기값 설정
- initGlobal, initLocal, initComputed - 데이터 바인딩 병합 (LayoutLoader)
- scrollIntoView 핸들러
- 위지윅 레이아웃 편집기 관련 전역 API (G7CoreGlobals)
Fixed
- navigate/back 재진입 시 init_actions 데이터소스 유실 수정 (TemplateApp)
- sequence 핸들러 _local 상태 동기화 수정
- 배열 쿼리 파라미터 처리 버그 수정 (TemplateApp, ApiClient, Router, ActionDispatcher, DataSourceManager)
- dataKey 바인딩 버그 수정 (TemplateApp)
[engine-v1.10.0]
Added
- conditions 속성 - AND/OR 그룹 및 if-else 체인 조건부 렌더링 (DynamicRenderer)
- conditions 액션 - 조건부 액션 실행 (ActionDispatcher)
- conditions 속성 - 조건부 데이터 소스 로딩 (DataSourceManager)
- conditions 속성 - 조건부 레이아웃 로드 (LayoutLoader)
- slotId 동적 결정 - 표현식 지원으로 동적 슬롯 결정
- zIndex 속성 - 렌더링 순서 제어 (기본값: 0)
- SlotContext 시스템 모듈
- 배열 쿼리 파라미터 (navigate 핸들러)
Fixed
- setState deep merge 및 배열 쿼리스트링 처리 수정
- Slot 컴포넌트 에러 및 검색 편집 모드 에러 수정
- 쿼리 스트링 기반 검색 필터 미설정 수정
[engine-v1.9.0]
Added
- _defines - 컴포넌트에서 {{_defines.xxx}}로 접근 가능한 정의 변수
- 파이프 함수 (
|uppercase,|lowercase등) - $get 헬퍼 함수 - 안전한 중첩 속성 접근
- $switch 표현식 - 다중 분기 값 선택
- classMap - 조건부 CSS 클래스 (key → variants 매핑)
- computed - 계산된 값 시스템
- switch params.value 지원, default 케이스 지원
Fixed
- 레이아웃 서빙 시 defines/computed 속성 미포함 수정
- validation 에러 성공 후 미클리어 수정
- 액션 핸들러 캐시 문제 수정 (ActionDispatcher)
- resolveValue 호출 캐시 문제 수정 (ActionDispatcher)
[engine-v1.8.0]
Added
- 동적 레이아웃 로드 시스템 (LayoutLoader)
- 외부 스크립트 동적 로드 (scripts 속성)
- if 조건을 사용한 조건부 로드
Fixed
- dataKey 자동바인딩 버그 수정 (DynamicRenderer)
- data_source refetchOnMount 속성 누락 수정
- iterator 타입 지원 및 에러 수정 (DynamicRenderer, RenderHelpers)
- 에러 객체 deep merge 처리 수정 (ActionDispatcher)
[engine-v1.7.0]
Added
- loadScript 핸들러 - 외부 JavaScript 동적 로드
- callExternal 핸들러 - 외부 함수 호출
Fixed
- 설정 탭 모듈 목록 iteration 버그 수정 (DynamicRenderer)
- 점진적 콘텐츠 에러 핸들링 실패 수정 (TemplateApp, ActionDispatcher, DynamicRenderer)
- refetchDataSource 시 blur_until_loaded 미적용 수정 (TemplateApp)
- blur_until_loaded 개별 DOM 요소 처리 수정
[engine-v1.6.0]
Added
- errorHandling - 레이아웃 레벨 에러 핸들링 설정
- showErrorPage 핸들러 - 에러 페이지 표시
[engine-v1.5.0]
Added
- blur_until_loaded 표현식 지원 (
{{_global.isSaving}}등 동적 조건)
Fixed
- 복잡한 조건식이 포함된 다국어 문자열 처리 오류 수정 (TranslationEngine)
- 번역 파라미터로 공백 문자열 전달 시 파싱 오류 수정 (TranslationEngine)
- apiCall 액션에 auth_required 옵션 누락 수정 (ActionDispatcher)
- data_source.endpoint 표현식 미처리 수정 (DataSourceManager)
[engine-v1.4.0]
Added
- 조건부 데이터 소스 로딩 (if 속성) - 생성/수정 모드 분기
Fixed
- 데이터그리드 표현식 렌더링 오류 수정
- validation error 미표시 수정
[engine-v1.2.0]
Added
- 모달 스택 - 중첩 모달 지원 (_global.modalStack)
- 멀티 모달 지원
- 반복 컨텍스트 바인딩 (iteration 내 표현식)
- text 속성 복합 바인딩
Fixed
- ModalWrapper 데이터 바인딩 시 캐시 문제 수정 (ModalDataSourceWrapper)
- ModalWrapper 렌더링 시 iteration 미작동 수정 (DynamicRenderer)
- 모달 오픈 시 API 기반 바인딩 미동작 수정 (ModalDataSourceWrapper)
- 모달 클릭 시 API 응답 에러 처리 미개선 수정
- 모달 렌더링 오류 수정 (DynamicRenderer)
- 반복 컨텍스트에서 파라미터가 포함된 다국어 번역 미작동 수정 (DynamicRenderer, RenderHelpers)
- 다국어 파라미터로 데이터 미전달 수정
[engine-v1.1.0]
Added
- 다크 모드 지원 (Tailwind dark: variant)
- 반응형 레이아웃 (responsive 속성)
- 전역 상태 관리 (_global, _local)
- 데이터 바인딩 및 표현식 ({{user.name}}, {{route.id}})
- Optional Chaining & Nullish Coalescing 지원
Fixed
- 언어 변경 후 navigate 핸들러 미작동 수정 (TemplateApp)
- 페이지네이션 오류, DataGrid 렌더링 오류 수정
- 검색어 입력 후 엔터 키 미반응 수정
- 검색 미작동, queryString 미반영 수정
- 체크박스 동작 오류 — preventDefault 누락 수정
- 로그인 시 ActionDispatcher actions 미실행 수정 (TemplateApp, Router)
- 토큰 만료 후 refresh 토큰 / 로그인 리다이렉션 미작동 수정
- 렌더링 오류 수정 (DataBindingEngine)
- 템플릿 엔진 로더/렌더링 오류 (hotfix)