docs: define MSIX-first packaging design and roadmap

Document package formats, release workflows, deployment planning and UI concepts.

Closes #1
This commit is contained in:
jychoi4 committed 2026-10-27 16:30:00 +09:00
1 parent 54f0cfe360
commit 24b624301c
37 files changed
+7061 -61

No files matched your search

File diff suppressed because it is too large. Load diff
+164
View File
@@ -0,0 +1,164 @@
<style>
#eg-wizard-design { width:100%; }
#eg-wizard-design .ew-stage { min-height:690px; }
#eg-wizard-design .ew-preview,#eg-wizard-design .ew-app { --ew-bg:light-dark(#ffffff,#20252d); --ew-soft:light-dark(#f4f6f9,#191e26); --ew-line:light-dark(#dce2ea,#3c4553); --ew-ink:light-dark(#202c3d,#edf2fa); --ew-muted:light-dark(#5f6d80,#b0bdce); --ew-blue:light-dark(#2656bd,#a6c0ff); --ew-on:light-dark(#ffffff,#112956); --ew-wash:light-dark(#edf3ff,#293a57); --ew-good:light-dark(#246b50,#94d9b7); --ew-warn:light-dark(#865517,#efc280); --ew-bad:light-dark(#a63532,#ffb0ac); color:var(--ew-ink); font:14px/1.6 'Segoe UI','Malgun Gothic',sans-serif; }
#eg-wizard-design * { box-sizing:border-box; }
#eg-wizard-design .ew-preview { display:flex; flex-wrap:wrap; align-items:flex-end; gap:8px; padding:14px 0; padding-right:92px; }
#eg-wizard-design .ew-preview label { display:grid; gap:4px; font-size:11px; color:var(--ew-muted); }
#eg-wizard-design .ew-preview select { max-width:100%; background:var(--ew-bg); color:var(--ew-ink); border:1px solid var(--ew-line); border-radius:5px; padding:6px 8px; font:12px/1.5 'Segoe UI','Malgun Gothic',sans-serif; }
#eg-wizard-design button { font:500 12px/1.5 'Segoe UI','Malgun Gothic',sans-serif; }
#eg-wizard-design .ew-button { display:inline-flex; justify-content:center; align-items:center; gap:7px; min-height:34px; padding:7px 16px; border:1px solid var(--ew-line); border-radius:5px; color:var(--ew-ink); background:var(--ew-bg); }
#eg-wizard-design button:disabled { opacity:.48; cursor:not-allowed; }
#eg-wizard-design .ew-primary { background:var(--ew-blue); color:var(--ew-on); border-color:var(--ew-blue); }
#eg-wizard-design .ew-link { background:transparent; border:0; color:var(--ew-blue); padding:5px 0; }
#eg-wizard-design .ew-app { background:var(--ew-bg); border:1px solid var(--ew-line); border-radius:9px; overflow-wrap:anywhere; box-shadow:0 8px 30px light-dark(#17305710,#00000025); }
#eg-wizard-design .ew-title { min-height:44px; display:flex; gap:9px; align-items:center; padding:10px 18px; border-bottom:1px solid var(--ew-line); font-size:12px; }
#eg-wizard-design .ew-mark { display:grid; place-items:center; width:23px; height:23px; background:var(--ew-blue); color:var(--ew-on); border-radius:5px; font-weight:600; flex-shrink:0; }
#eg-wizard-design .ew-tag { margin-left:auto; font-size:11px; color:var(--ew-muted); }
#eg-wizard-design .ew-layout { display:grid; grid-template-columns:154px minmax(0,1fr); min-height:480px; }
#eg-wizard-design .ew-side { background:var(--ew-soft); padding:28px 18px; border-right:1px solid var(--ew-line); }
#eg-wizard-design .ew-side-brand { font-size:17px; font-weight:600; letter-spacing:-.6px; margin-bottom:4px; }
#eg-wizard-design .ew-side-sub { color:var(--ew-muted); font-size:11px; }
#eg-wizard-design .ew-steps { list-style:none; display:grid; gap:21px; margin:34px 0 0; padding:0; }
#eg-wizard-design .ew-steps li { display:flex; align-items:center; gap:9px; color:var(--ew-muted); font-size:12px; }
#eg-wizard-design .ew-number { display:grid; place-items:center; width:22px; height:22px; border:1px solid var(--ew-line); border-radius:50%; font-size:11px; flex-shrink:0; }
#eg-wizard-design .ew-steps [aria-current=step] { color:var(--ew-blue); font-weight:600; }
#eg-wizard-design .ew-steps [aria-current=step] .ew-number { background:var(--ew-blue); color:var(--ew-on); border-color:transparent; }
#eg-wizard-design .ew-past .ew-number { color:var(--ew-good); }
#eg-wizard-design .ew-main { min-width:0; }
#eg-wizard-design .ew-page-heading { padding:23px 26px 18px; border-bottom:1px solid var(--ew-line); }
#eg-wizard-design .ew-page-heading h2 { margin:0 0 5px; font-size:19px; font-weight:600; letter-spacing:-.6px; }
#eg-wizard-design .ew-page-heading p { margin:0; color:var(--ew-muted); font-size:12px; }
#eg-wizard-design .ew-body { padding:22px 26px; }
#eg-wizard-design .ew-body h3 { font-size:15px; margin:0 0 10px; font-weight:600; }
#eg-wizard-design .ew-body p { margin:0 0 14px; font-size:13px; }
#eg-wizard-design .ew-muted { color:var(--ew-muted); }
#eg-wizard-design .ew-small { font-size:11px !important; }
#eg-wizard-design .ew-box { padding:14px 16px; border:1px solid var(--ew-line); border-radius:6px; margin:14px 0; background:var(--ew-bg); }
#eg-wizard-design .ew-box-top { display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap; margin-bottom:7px; }
#eg-wizard-design .ew-chip { display:inline-block; color:var(--ew-good); background:var(--ew-soft); font-size:11px; padding:2px 7px; border-radius:4px; }
#eg-wizard-design .ew-path { display:block; font-size:12px; color:var(--ew-muted); overflow-wrap:anywhere; }
#eg-wizard-design .ew-note { display:flex; align-items:flex-start; gap:9px; padding:12px; background:var(--ew-wash); color:var(--ew-blue); border-radius:5px; font-size:12px; margin-top:14px; }
#eg-wizard-design .ew-note svg { flex-shrink:0; margin-top:2px; }
#eg-wizard-design .ew-list { padding:0; margin:0; list-style:none; }
#eg-wizard-design .ew-list li { display:flex; justify-content:space-between; gap:12px; padding:9px 0; font-size:12px; border-bottom:1px solid var(--ew-line); }
#eg-wizard-design .ew-list li:last-child { border:0; }
#eg-wizard-design .ew-good { color:var(--ew-good); }
#eg-wizard-design .ew-bad { color:var(--ew-bad); }
#eg-wizard-design .ew-status { font-size:11px; white-space:nowrap; }
#eg-wizard-design .ew-field { display:grid; gap:6px; font-size:12px; margin:0 0 16px; }
#eg-wizard-design .ew-field input { width:100%; border:1px solid var(--ew-line); border-radius:5px; padding:9px 10px; background:var(--ew-bg); color:var(--ew-ink); font:12px/1.5 'Segoe UI','Malgun Gothic',sans-serif; }
#eg-wizard-design .ew-choice { display:flex; align-items:flex-start; gap:8px; padding:9px 0; font-size:12px; }
#eg-wizard-design .ew-choice input { accent-color:var(--ew-blue); margin:3px 0 0; flex-shrink:0; }
#eg-wizard-design .ew-choice span { display:block; }
#eg-wizard-design .ew-choice small { display:block; color:var(--ew-muted); font-size:11px; margin-top:2px; }
#eg-wizard-design .ew-rule { height:1px; background:var(--ew-line); margin:14px 0; }
#eg-wizard-design .ew-summary { display:grid; grid-template-columns:88px minmax(0,1fr); gap:10px 14px; font-size:12px; margin:0; }
#eg-wizard-design .ew-summary dt { color:var(--ew-muted); }
#eg-wizard-design .ew-summary dd { margin:0; }
#eg-wizard-design .ew-progress { background:var(--ew-soft); height:8px; border-radius:4px; overflow:hidden; margin:18px 0 7px; }
#eg-wizard-design .ew-progress span { display:block; background:var(--ew-blue); height:100%; }
#eg-wizard-design .ew-progress-label { display:flex; justify-content:space-between; gap:12px; color:var(--ew-muted); font-size:12px; margin-bottom:15px; }
#eg-wizard-design .ew-footer { border-top:1px solid var(--ew-line); background:var(--ew-soft); padding:14px 18px; border-radius:0 0 9px 9px; display:flex; justify-content:space-between; align-items:center; gap:10px; flex-wrap:wrap; }
#eg-wizard-design .ew-footer-label { color:var(--ew-muted); font-size:11px; }
#eg-wizard-design .ew-actions { display:flex; gap:7px; flex-wrap:wrap; margin-left:auto; }
#eg-wizard-design .ew-symbol { display:grid; place-items:center; width:42px; height:42px; border-radius:50%; background:var(--ew-wash); color:var(--ew-blue); font-size:23px; margin:0 0 15px; }
#eg-wizard-design .ew-log { margin-top:16px; color:var(--ew-muted); font-size:11px; }
#eg-wizard-design .ew-log summary { width:fit-content; }
#eg-wizard-design .ew-log pre { white-space:pre-wrap; overflow-wrap:anywhere; font:11px/1.7 'Segoe UI','Malgun Gothic',sans-serif; }
#eg-wizard-design .ew-confirm { background:var(--ew-bg); border:1px solid var(--ew-line); border-radius:6px; padding:16px; margin-top:16px; }
#eg-wizard-design .ew-confirm .ew-actions { margin-top:12px; }
#eg-wizard-design .ew-compact .ew-layout { display:block; }
#eg-wizard-design .ew-compact .ew-side { display:none; }
#eg-wizard-design .ew-compact .ew-page-heading { padding-left:30px; padding-right:30px; background:var(--ew-soft); }
#eg-wizard-design .ew-compact .ew-body { max-width:610px; padding:24px 30px; margin:auto; }
#eg-wizard-design .ew-caption { color:var(--ew-muted); font-size:11px; margin:10px 0 0; }
@media(max-width:580px) { #eg-wizard-design .ew-stage { min-height:850px; } #eg-wizard-design .ew-layout { display:block; } #eg-wizard-design .ew-side { padding:12px 16px; border-right:0; border-bottom:1px solid var(--ew-line); } #eg-wizard-design .ew-side-brand,#eg-wizard-design .ew-side-sub { display:none; } #eg-wizard-design .ew-steps { display:flex; gap:7px; justify-content:space-between; margin:0; } #eg-wizard-design .ew-steps li { flex-direction:column; gap:5px; font-size:11px; } #eg-wizard-design .ew-page-heading,#eg-wizard-design .ew-compact .ew-page-heading { padding:18px; } #eg-wizard-design .ew-body,#eg-wizard-design .ew-compact .ew-body { padding:18px; } #eg-wizard-design .ew-preview { padding-right:0; padding-top:55px; } #eg-wizard-design .ew-title { padding:10px 14px; } #eg-wizard-design .ew-summary { grid-template-columns:70px minmax(0,1fr); } }
@media(pointer:coarse) { #eg-wizard-design button,#eg-wizard-design select,#eg-wizard-design .ew-choice { min-height:44px; } #eg-wizard-design .ew-field input { font-size:16px; } }
</style>
<div id="eg-wizard-design" class="viz-carousel viz-dotted-background" aria-label="EG-BIM 설치 마법사 재디자인" data-previous-label="이전 디자인" data-next-label="다음 디자인">
<section data-variant="진행 안내형" class="ew-stage" aria-label="진행 안내형 설치 마법사"><div id="ew-guided-tools" class="ew-preview"></div><div id="ew-guided" class="ew-app" aria-label="진행 안내형"></div></section>
<section data-variant="표준 마법사형" class="ew-stage" aria-label="표준 마법사형 설치 마법사" hidden><div id="ew-standard-tools" class="ew-preview"></div><div id="ew-standard" class="ew-app ew-compact" aria-label="표준 마법사형"></div></section>
</div>
<script>
(() => {
const root=document.getElementById('eg-wizard-design');
const escape=v=>String(v).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const modes={new:'신규 설치',upgrade:'기존 버전 전환',patch:'버전 패치',repair:'설치 복구',remove:'제거'};
const scenarios={normal:'정상 완료',backupFail:'백업 실패',applyFail:'설치 실패',reboot:'재부팅 필요',locked:'실행 중인 앱'};
const actions={new:'설치',upgrade:'업데이트',patch:'패치 적용',repair:'복구',remove:'제거'};
const basePath='C:\\Program Files\\Baron\\EG-BIM', legacyPath='D:\\Baron\\EG-BIM';
const defaults=()=>({mode:'upgrade',scenario:'normal',page:'welcome',agreed:false,path:basePath,resources:true,shortcut:true,removeConfirmed:false,launch:true,restart:false,confirmCancel:false,cancelled:false,error:'',closed:false});
function sequence(s) { return s.mode==='new'?['welcome','license','options','review','prepare','apply','finish']:['welcome','review','prepare','apply','finish']; }
function normalize(input) { const s=defaults(); if(!input||typeof input!=='object')return s; for(const key of Object.keys(s)){if(typeof input[key]===typeof s[key])s[key]=input[key];} if(!modes[s.mode])s.mode='upgrade';if(!scenarios[s.scenario])s.scenario='normal';if(![...sequence(s),'failed','cancelled'].includes(s.page))s.page='welcome'; if(s.scenario==='backupFail'&&!['upgrade','patch'].includes(s.mode))s.scenario='normal';return s; }
const saved=window.openai?.widgetState?.privateContent?.wizards||{};
const states={'ew-guided':normalize(saved['ew-guided']),'ew-standard':normalize(saved['ew-standard'])};
function next(s) {
s.error='';
if(s.page==='license'&&!s.agreed){s.error='사용권 계약에 동의해야 다음 단계로 이동할 수 있습니다.';return;}
if(s.page==='options'&&!/^[a-zA-Z]:\\[^<>:"|?*\r\n]+$/.test(s.path)){s.error='드라이브를 포함한 설치 경로를 입력해 주세요. 예: C:\\Program Files\\Baron\\EG-BIM';return;}
if(s.page==='review'&&s.mode==='remove'&&!s.removeConfirmed){s.error='제거할 항목을 확인하고 동의해 주세요.';return;}
const seq=sequence(s),index=seq.indexOf(s.page); if(index>=0&&index<seq.length-1)s.page=seq[index+1];
}
function simulate(s) {
if(s.confirmCancel)return;
if(s.page==='prepare') {
if(s.scenario==='locked'){s.error='EG-BIM에서 도면을 저장하고 종료한 후 다시 확인해 주세요.';return;}
s.page=s.scenario==='backupFail'?'failed':'apply';
} else if(s.page==='apply')s.page=s.cancelled||s.scenario==='applyFail'?'failed':'finish';
}
function row(label,value,tone='') {return `<li><span>${label}</span><span class="ew-status ${tone}">${value}</span></li>`;}
function note(text,icon='shield-check') {return `<div class="ew-note"><i data-lucide="${icon}" aria-hidden="true"></i><span>${text}</span></div>`;}
function choice(label,key,checked,sub='',disabled=false) {return `<label class="ew-choice"><input type="checkbox" data-value="${key}" ${checked?'checked':''} ${disabled?'disabled':''}><span>${label}${sub?`<small>${sub}</small>`:''}</span></label>`;}
function title(s) {
const a=actions[s.mode];
return {welcome:[s.mode==='new'?'EG-BIM 설치를 시작합니다':s.mode==='upgrade'?'기존 EG-BIM을 업데이트합니다':s.mode==='patch'?'새 버전의 변경 사항을 적용합니다':s.mode==='repair'?'EG-BIM 설치를 복구합니다':'EG-BIM을 제거합니다','계속하려면 내용을 확인하고 다음을 선택하세요.'],license:['사용권 계약','설치하기 전에 사용권 계약을 확인해 주세요.'],options:['설치 위치 및 옵션','설치할 위치와 추가 항목을 선택하세요.'],review:[`${a} 준비가 되었습니다`,'아래 내용을 확인하세요. 이전 단계로 돌아가 선택을 바꿀 수 있습니다.'],prepare:['설치 환경을 준비하고 있습니다','작업을 저장하고 실행 중인 EG-BIM을 종료해 주세요.'],apply:[s.cancelled?'취소 요청을 처리하고 있습니다':`${a} 작업을 진행하고 있습니다`,s.cancelled?'안전한 중단 지점과 현재 설치 상태를 확인합니다.':'작업이 끝날 때까지 PC를 끄지 마세요.'],finish:[s.scenario==='reboot'?'완료하려면 PC를 다시 시작해야 합니다':`${a} 작업이 완료되었습니다`,s.scenario==='reboot'?'열려 있는 작업을 저장한 후 다시 시작해 주세요.':'아래 내용을 확인하고 마침을 선택하세요.'],failed:[s.scenario==='backupFail'?'설정 백업을 완료하지 못했습니다':s.cancelled?'작업을 중단했습니다':'설치를 완료하지 못했습니다',s.scenario==='backupFail'?'기존 제품을 변경하지 않았습니다.':'현재 상태를 확인한 후 복구 절차를 진행해야 합니다.'],cancelled:['설치를 취소했습니다','프로그램을 변경하기 전에 종료했습니다.']}[s.page];
}
function body(s) {
const a=actions[s.mode], existing=s.mode!=='new', backup=['upgrade','patch'].includes(s.mode), path=existing?legacyPath:s.path;
if(s.page==='welcome')return `<div class="ew-symbol"><i data-lucide="${s.mode==='remove'?'package-minus':s.mode==='repair'?'wrench':'package-check'}" aria-hidden="true"></i></div><h3>${s.mode==='new'?'설계에 필요한 도구를 준비하세요.':s.mode==='upgrade'?'작업 환경은 유지하고, 프로그램은 새롭게.':s.mode==='patch'?'설치된 구성은 그대로 유지합니다.':s.mode==='repair'?'프로그램 파일과 설치 정보를 복구합니다.':'프로그램과 설치 항목을 제거합니다.'}</h3><p class="ew-muted">${s.mode==='remove'?'개인 도면, 사용자 설정과 보관된 백업은 유지합니다.':s.mode==='new'?'마법사의 안내에 따라 설치 위치와 옵션을 선택할 수 있습니다.':'작업 중인 도면을 저장한 후 진행해 주세요.'}</p><div class="ew-box"><div class="ew-box-top"><strong>EG-BIM</strong><span class="ew-chip">${existing?'기존 설치 확인':'Windows · x64'}</span></div><span class="ew-path">${escape(path)}</span><span class="ew-small ew-muted">${existing?'기존 설치 위치를 그대로 사용합니다.':'기본 설치 위치 · 다음 단계에서 변경 가능'}</span></div>${note(backup?'설정 백업을 확인한 후 프로그램 변경을 시작합니다.':s.mode==='remove'?'타사 CAD 연결과 사용자 데이터는 삭제 대상에서 제외합니다.':'시스템 변경이 필요한 경우 Windows가 관리자 권한을 요청합니다.')}`;
if(s.page==='license')return `<div class="ew-box"><h3>EG-BIM 소프트웨어 사용권 계약</h3><p class="ew-muted">설치 화면 검토용 약관 자리표시자입니다.</p><p class="ew-small ew-muted">실제 설치 파일에는 제품 담당자가 승인한 약관 전문과 버전을 표시합니다. 이 시안의 선택은 법적 계약 동의나 실제 설치를 수행하지 않습니다.</p></div>${choice('사용권 계약에 동의합니다.','agreed',s.agreed)}${note('동의 여부는 설치 시작 전까지 변경할 수 있습니다.','info')}`;
if(s.page==='options')return `<label class="ew-field">설치 위치<input data-path aria-label="설치 위치" value="${escape(s.path)}" spellcheck="false"></label><h3>설치 구성</h3>${choice('EG-BIM 프로그램','core',true,'필수 구성요소',true)}${choice('추가 리소스','resources',s.resources,'기능 선택 예시 · 실제 리소스 목록은 별도 확정')}<div class="ew-rule"></div>${choice('바탕 화면에 바로가기 만들기','shortcut',s.shortcut)}<p class="ew-small ew-muted">기존 CAD 파일 연결은 자동으로 변경하지 않습니다.</p>`;
if(s.page==='review')return `<dl class="ew-summary"><dt>작업</dt><dd>${modes[s.mode]}</dd><dt>${s.mode==='remove'?'제거 위치':'설치 위치'}</dt><dd>${escape(path)}${existing&&s.mode!=='remove'?' · 유지':''}</dd><dt>설치 구성</dt><dd>${s.mode==='remove'?'현재 설치된 프로그램 제거':existing?'현재 설치된 기능 유지':s.resources?'프로그램 + 추가 리소스':'프로그램'}</dd><dt>사용자 데이터</dt><dd>${backup?'설정·CUI·추가 리소스 백업 후 호환 설정 복원':s.mode==='remove'?'개인 도면·설정·백업 유지':'기존 데이터 유지'}</dd>${s.mode==='new'?`<dt>바로가기</dt><dd>${s.shortcut?'바탕 화면에 생성':'생성 안 함'}</dd>`:''}<dt>필수 구성</dt><dd>${s.mode==='remove'?'공유 런타임 유지':'필요한 런타임만 준비'}</dd></dl>${s.mode==='remove'?choice('프로그램을 제거하고 개인 데이터는 유지합니다.','removeConfirmed',s.removeConfirmed):note(backup?'파일 확보와 백업 검증에 실패하면 기존 제품을 제거하지 않습니다.':'설치 전 패키지와 저장 공간을 다시 확인합니다.')}<details class="ew-log"><summary class="cursor-interaction">예정 작업 자세히 보기</summary><p>환경 검사 → 파일 검증 → ${backup?'앱 종료 확인 · 설정 백업 → ':''}${a} → 상태 확인${backup?' · 설정 복원':''}</p></details>`;
if(s.page==='prepare')return `<ul class="ew-list">${row('지원 환경 · 저장 공간','확인 완료','ew-good')}${row('설치 파일 · 서명 · 무결성','확인 완료','ew-good')}${row('실행 중인 프로그램',s.scenario==='locked'?'EG-BIM 실행 중':'종료 확인',s.scenario==='locked'?'ew-bad':'ew-good')}${backup?row('사용자 설정 백업',s.scenario==='locked'?'대기':'검사 중'):row('실행 계획 준비','준비 중')}</ul>${s.scenario==='locked'?`${note('도면을 직접 저장하고 EG-BIM을 종료해 주세요. 강제 종료하지 않습니다.','info')}<button type="button" class="ew-button cursor-interaction" data-action="recheck" style="margin-top:12px">다시 확인</button>`:note(backup?'백업 위치: D:\\EG-BIM-Backup\\시도-001 (예시)':'현재까지 프로그램 파일은 변경하지 않았습니다.')}<p class="ew-small ew-muted" style="margin-top:16px">시안 진행은 창 위의 ‘작업 진행’으로 확인할 수 있습니다.</p>`;
if(s.page==='apply')return `<h3>${s.cancelled?'안전한 중단 지점 확인 중':s.mode==='remove'?'등록된 프로그램 파일 제거 중':'프로그램 파일 적용 중'}</h3><div class="ew-progress" role="progressbar" aria-label="진행률 예시" aria-valuemin="0" aria-valuemax="100" aria-valuenow="64"><span style="width:64%"></span></div><div class="ew-progress-label"><span>${s.cancelled?'취소 요청됨':'현재 작업 진행률'}</span><span>64%</span></div><ul class="ew-list">${row('환경 및 설치 파일 확인','완료','ew-good')}${backup?row('사용자 설정 백업 검증','완료','ew-good'):''}${row(a,s.cancelled?'중단 요청 처리 중':'진행 중')}${row('설치 상태 확인','대기')}</ul><details class="ew-log"><summary class="cursor-interaction">작업 내역 보기</summary><pre>시도: DEMO-001\n환경 검사: 완료\n패키지 검증: 완료\n${backup?'백업 검증: 완료\n':''}작업 상태: ${s.cancelled?'취소 처리 중':'적용 중'}\n이 내역은 시안용 예시입니다.</pre></details>`;
if(s.page==='failed')return `<div class="ew-symbol">!</div><h3>${s.scenario==='backupFail'?'백업 저장 위치를 확인해 주세요.':'진단 기록을 보관해 주세요.'}</h3><p class="ew-muted">${s.scenario==='backupFail'?'백업 위치의 공간 또는 쓰기 권한을 확인한 후 다시 검사할 수 있습니다.':'현재 설치 상태가 확인되지 않았습니다. 설치 파일을 다시 실행하거나 전체 설치로 덮어쓰기 전에 복구 상태를 확인해야 합니다.'}</p><ul class="ew-list">${row('프로그램 상태',s.scenario==='backupFail'?'기존 상태 유지':'확인 필요',s.scenario==='backupFail'?'ew-good':'ew-bad')}${row('백업',backup?(s.scenario==='backupFail'?'검증되지 않음':'보관됨'):'해당 없음')}${row('다음 조치',s.scenario==='backupFail'?'백업 위치 확인':'진단 및 복구')}</ul><details class="ew-log" open><summary class="cursor-interaction">진단 기록</summary><pre>시도: DEMO-001\n상태: ${s.scenario==='backupFail'?'BackupValidationFailed':s.cancelled?'CancellationStateUncertain':'InstallStateUncertain'}\n로그: 설치 로그 보관 위치 (예시)\n이 시안은 실제 시스템 상태를 조회하지 않습니다.</pre></details>`;
if(s.page==='cancelled')return `<div class="ew-symbol">−</div><h3>다음에 다시 시작할 수 있습니다.</h3><p class="ew-muted">설치 전 취소되어 기존 프로그램과 사용자 데이터는 유지됩니다.</p>`;
const reboot=s.scenario==='reboot';
return `<div class="ew-symbol">${reboot?'↻':'✓'}</div><ul class="ew-list">${row(a,'완료','ew-good')}${backup?row('호환 설정 복원','완료','ew-good'):''}${row('설치 상태',reboot?'재부팅 후 확인':'확인 완료',reboot?'':'ew-good')}</ul>${reboot?`${choice('지금 다시 시작','restart',s.restart,'선택하지 않으면 나중에 직접 재부팅합니다.')}<p class="ew-small ew-muted">시안에서는 실제 재부팅하지 않습니다.</p>`:s.mode!=='remove'?choice('마침 후 EG-BIM 실행','launch',s.launch,'원래 사용자 권한으로 실행'):'<p class="ew-small ew-muted" style="margin-top:15px">개인 도면·사용자 설정·백업은 유지됩니다.</p>'}${backup?note('백업은 유지됩니다. 도면·출력·인증·플러그인을 확인한 후 정리하세요.'):''}${s.closed?'<p class="ew-small ew-muted" role="status" style="margin-top:12px">미리보기를 마쳤습니다. 실제 실행이나 재부팅은 하지 않았습니다.</p>':''}`;
}
function persist(id) {window.openai?.setWidgetState?.({modelContent:{design:id==='ew-guided'?'진행 안내형':'표준 마법사형',mode:states[id].mode,page:states[id].page,scenario:states[id].scenario},privateContent:{wizards:states}})?.catch(()=>{});}
function render(id,focus=false) {
const s=states[id],app=document.getElementById(id),tools=document.getElementById(id+'-tools');
tools.innerHTML=`<label>시안 · 설치 상황<select class="cursor-interaction" data-mode aria-label="시안 설치 상황">${Object.entries(modes).map(([k,v])=>`<option value="${k}" ${s.mode===k?'selected':''}>${v}</option>`).join('')}</select></label><label>시험할 상태<select class="cursor-interaction" data-scenario aria-label="시험할 상태">${Object.entries(scenarios).filter(([k])=>k!=='backupFail'||['upgrade','patch'].includes(s.mode)).map(([k,v])=>`<option value="${k}" ${s.scenario===k?'selected':''}>${v}</option>`).join('')}</select></label><button class="ew-button cursor-interaction" data-demo="advance" ${!['prepare','apply'].includes(s.page)||s.confirmCancel?'disabled':''}>작업 진행</button><button class="ew-button cursor-interaction" data-demo="reset">처음부터</button>`;
tools.querySelector('[data-mode]').addEventListener('change',e=>{states[id]=defaults();states[id].mode=e.target.value;render(id);persist(id);});
tools.querySelector('[data-scenario]').addEventListener('change',e=>{s.scenario=e.target.value;s.page='welcome';s.error='';s.cancelled=false;s.confirmCancel=false;s.closed=false;render(id);persist(id);});
tools.querySelector('[data-demo=advance]').addEventListener('click',()=>{simulate(s);render(id);persist(id);});
tools.querySelector('[data-demo=reset]').addEventListener('click',()=>{states[id]={...defaults(),mode:s.mode,scenario:s.scenario};render(id);persist(id);});
const [heading,sub]=title(s),seq=sequence(s),index=seq.indexOf(s.page);
const groups=s.mode==='new'?['시작','선택','확인','진행','완료']:['시작','확인','진행','완료'];
const current=(s.mode==='new'?{welcome:0,license:1,options:1,review:2,prepare:3,apply:3,finish:4,failed:3,cancelled:0}:{welcome:0,review:1,prepare:2,apply:2,finish:3,failed:2,cancelled:0})[s.page];
const busy=['prepare','apply'].includes(s.page),end=['finish','failed','cancelled'].includes(s.page);
const button= end?(s.page==='failed'&&s.scenario==='backupFail'?'다시 검사':s.page==='finish'?'마침':'닫기'):s.page==='review'?`${actions[s.mode]} 시작`:busy?s.cancelled?'취소 처리 중…':'진행 중…':'다음';
app.innerHTML=`<header class="ew-title"><span class="ew-mark">E</span><span>EG-BIM 설치 마법사</span><span class="ew-tag">${modes[s.mode]}</span></header><div class="ew-layout"><aside class="ew-side"><div class="ew-side-brand">EG-BIM</div><div class="ew-side-sub">BARON SOFTWARE</div><ol class="ew-steps">${groups.map((n,i)=>`<li ${i===current?'aria-current="step"':''} class="${i<current?'ew-past':''}"><span class="ew-number">${i<current?'✓':i+1}</span>${n}</li>`).join('')}</ol></aside><main class="ew-main"><header class="ew-page-heading"><h2>${heading}</h2><p>${sub}</p></header><div class="ew-body"><div aria-live="polite">${body(s)}</div>${s.error?`<p class="ew-small ew-bad" role="alert">${escape(s.error)}</p>`:''}${s.confirmCancel?`<section class="ew-confirm" aria-label="취소 확인"><h3>작업을 취소하시겠습니까?</h3><p class="ew-small ew-muted">${s.page==='apply'?'안전한 중단 지점까지 기다린 후 설치 상태를 확인합니다.':'아직 프로그램을 변경하지 않았습니다.'}</p><div class="ew-actions"><button class="ew-button cursor-interaction" data-action="keep">계속 진행</button><button class="ew-button cursor-interaction" data-action="confirm">취소 요청</button></div></section>`:''}</div></main></div><footer class="ew-footer"><span class="ew-footer-label">시안 · 표시 정보는 예시</span><div class="ew-actions"><button class="ew-button cursor-interaction" data-action="back" ${index<=0||busy||end||s.confirmCancel?'disabled':''}>이전</button><button class="ew-button ew-primary cursor-interaction" data-action="next" ${busy||s.confirmCancel||s.page==='license'&&!s.agreed||s.page==='review'&&s.mode==='remove'&&!s.removeConfirmed?'disabled':''}>${button}</button>${!end?`<button class="ew-button cursor-interaction" data-action="cancel" ${s.cancelled||s.confirmCancel?'disabled':''}>취소</button>`:''}</div></footer>`;
app.querySelector('[data-path]')?.addEventListener('input',e=>{s.path=e.target.value;});
app.querySelectorAll('[data-value]').forEach(el=>el.addEventListener('change',e=>{s[el.dataset.value]=e.target.checked;render(id);persist(id);app.querySelector(`[data-value="${el.dataset.value}"]`)?.focus({preventScroll:true});}));
app.querySelector('[data-action=back]').addEventListener('click',()=>{if(index>0&&!busy&&!end){s.page=seq[index-1];s.error='';render(id,true);persist(id);}});
app.querySelector('[data-action=next]').addEventListener('click',()=>{
if(s.page==='failed'&&s.scenario==='backupFail'){s.scenario='normal';s.page='prepare';}
else if(end){s.closed=true;if(s.page!=='finish'){s.page='welcome';s.cancelled=false;s.closed=false;}}
else next(s);
render(id,true);persist(id);
});
app.querySelector('[data-action=cancel]')?.addEventListener('click',()=>{s.confirmCancel=true;render(id);app.querySelector('[data-action=keep]')?.focus({preventScroll:true});});
app.querySelector('[data-action=keep]')?.addEventListener('click',()=>{s.confirmCancel=false;render(id,true);});
app.querySelector('[data-action=confirm]')?.addEventListener('click',()=>{s.confirmCancel=false;if(s.page==='apply')s.cancelled=true;else s.page='cancelled';render(id,true);persist(id);});
app.querySelector('[data-action=recheck]')?.addEventListener('click',()=>{s.scenario='normal';s.error='';render(id);persist(id);});
if(globalThis.lucide)lucide.createIcons({attrs:{width:16,height:16}});
if(focus&&!busy)app.querySelector('[data-action=next]')?.focus({preventScroll:true});
}
Object.keys(states).forEach(id=>render(id));
window.addEventListener('openai:set_globals',e=>{const restored=e.detail?.globals?.widgetState?.privateContent?.wizards;if(restored)Object.keys(states).forEach(id=>{states[id]=normalize(restored[id]);render(id);});});
if(globalThis.Tweak)Object.keys(states).forEach(id=>{const app=document.getElementById(id),design={radius:9};const tweak=new Tweak({container:app,onChange:()=>{app.style.borderRadius=design.radius+'px';}});tweak.addSlider(design,'radius',{label:'창 모서리',min:0,max:16,unit:'px'});});
})();
</script>
+177
View File
@@ -0,0 +1,177 @@
<div id="msix-builder-concept">
<style>
#msix-builder-concept { font-family: "Segoe UI", "Malgun Gothic", sans-serif; color-scheme: light dark; }
#msix-builder-concept * { box-sizing: border-box; }
#msix-builder-concept [hidden] { display: none !important; }
#msix-builder-concept .ms-app { --ms-bg: light-dark(#f6f7fa,#171b23); --ms-paper: light-dark(#ffffff,#202630); --ms-text: light-dark(#202a39,#edf1f8); --ms-muted: light-dark(#59677b,#aebacf); --ms-line: light-dark(#dce2eb,#3a4353); --ms-accent: light-dark(#245bd7,#96b7ff); --ms-soft: light-dark(#edf3ff,#273752); --ms-button: light-dark(#245bd7,#a9c3ff); --ms-onbutton: light-dark(#ffffff,#152445); --ms-good: light-dark(#17654c,#8bd7b6); --ms-warn: light-dark(#805011,#edc27e); --ms-error: light-dark(#a52c33,#ffabb0); color:var(--ms-text); background:var(--ms-bg); border:1px solid var(--ms-line); border-radius:12px; overflow:hidden; min-height:940px; display:flex; flex-direction:column; font-size:14px; line-height:1.55; }
#msix-builder-concept button, #msix-builder-concept input, #msix-builder-concept select { font:inherit; }
#msix-builder-concept button { color:inherit; cursor:inherit; }
#msix-builder-concept .ms-top { display:flex; align-items:center; gap:12px; padding:16px 22px; padding-right:104px; border-bottom:1px solid var(--ms-line); background:var(--ms-paper); flex-wrap:wrap; }
#msix-builder-concept .ms-brand { font-size:16px; font-weight:600; display:flex; gap:10px; align-items:center; }
#msix-builder-concept .ms-mark { display:grid; place-items:center; width:32px; height:32px; border-radius:9px; background:var(--ms-button); color:var(--ms-onbutton); }
#msix-builder-concept .ms-pill { padding:3px 9px; border-radius:5px; background:var(--ms-soft); color:var(--ms-accent); font-size:12px; }
#msix-builder-concept .ms-caption { color:var(--ms-muted); font-size:12px; }
#msix-builder-concept .ms-spacer { flex:1; }
#msix-builder-concept .ms-layout { flex:1; display:grid; grid-template-columns:190px minmax(0,1fr); }
#msix-builder-concept .ms-nav { padding:23px 12px; border-right:1px solid var(--ms-line); }
#msix-builder-concept .ms-project { padding:0 12px 23px; overflow-wrap:anywhere; }
#msix-builder-concept .ms-project strong { display:block; font-weight:600; margin:4px 0; }
#msix-builder-concept .ms-nav button { display:flex; align-items:center; gap:11px; width:100%; border:0; background:transparent; padding:11px 12px; text-align:left; border-radius:7px; margin-bottom:5px; }
#msix-builder-concept .ms-nav button[aria-current="step"] { background:var(--ms-soft); color:var(--ms-accent); font-weight:600; }
#msix-builder-concept .ms-nav svg { flex:none; }
#msix-builder-concept .ms-nav .ms-nav-end { margin:30px 12px 0; padding-top:15px; border-top:1px solid var(--ms-line); }
#msix-builder-concept .ms-content { min-width:0; padding:27px 28px 20px; }
#msix-builder-concept h2 { margin:5px 0 7px; font-size:25px; line-height:1.35; font-weight:600; letter-spacing:-.6px; }
#msix-builder-concept h3 { margin:0 0 17px; font-size:15px; font-weight:600; }
#msix-builder-concept p { margin:0; }
#msix-builder-concept .ms-eyebrow { font-size:11px; letter-spacing:1.4px; font-weight:600; color:var(--ms-accent); }
#msix-builder-concept .ms-lead { color:var(--ms-muted); margin-bottom:25px; }
#msix-builder-concept .ms-cols { display:grid; grid-template-columns:minmax(0,1.65fr) minmax(190px,1fr); gap:20px; align-items:start; }
#msix-builder-concept .ms-panel { background:var(--ms-paper); border:1px solid var(--ms-line); border-radius:10px; padding:20px; min-width:0; }
#msix-builder-concept .ms-fields { display:grid; grid-template-columns:1fr 1fr; gap:17px; }
#msix-builder-concept .ms-full { grid-column:1/-1; }
#msix-builder-concept label.ms-field { display:flex; flex-direction:column; gap:6px; font-size:12px; font-weight:600; }
#msix-builder-concept input:not([type="checkbox"]), #msix-builder-concept select { width:100%; min-width:0; padding:9px 10px; border:1px solid var(--ms-line); border-radius:6px; color:var(--ms-text); background:var(--ms-paper); font-weight:400; }
#msix-builder-concept input:focus-visible, #msix-builder-concept select:focus-visible, #msix-builder-concept button:focus-visible { outline:2px solid var(--ms-accent); outline-offset:3px; }
#msix-builder-concept .ms-field small { color:var(--ms-muted); font-size:11px; font-weight:400; }
#msix-builder-concept .ms-line { height:1px; background:var(--ms-line); margin:20px 0; }
#msix-builder-concept .ms-note { margin-top:16px; padding:12px 14px; background:var(--ms-soft); border-radius:6px; color:var(--ms-muted); font-size:12px; }
#msix-builder-concept .ms-appicon { display:grid; place-items:center; width:56px; height:56px; border-radius:13px; background:var(--ms-button); color:var(--ms-onbutton); margin-bottom:17px; }
#msix-builder-concept .ms-appicon svg { width:27px; height:27px; }
#msix-builder-concept .ms-preview-name { font-size:19px; font-weight:600; overflow-wrap:anywhere; margin-bottom:4px; }
#msix-builder-concept .ms-preview-description { font-size:12px; color:var(--ms-muted); min-height:38px; overflow-wrap:anywhere; }
#msix-builder-concept .ms-kv { display:flex; align-items:flex-start; gap:14px; justify-content:space-between; font-size:12px; padding:8px 0; }
#msix-builder-concept .ms-kv span:first-child { color:var(--ms-muted); flex-shrink:0; }
#msix-builder-concept .ms-kv span:last-child { overflow-wrap:anywhere; text-align:right; min-width:0; }
#msix-builder-concept .ms-button { display:inline-flex; justify-content:center; align-items:center; gap:7px; background:var(--ms-paper); border:1px solid var(--ms-line); border-radius:6px; padding:9px 14px; font-weight:500; min-height:39px; }
#msix-builder-concept .ms-button.primary { background:var(--ms-button); border-color:transparent; color:var(--ms-onbutton); }
#msix-builder-concept .ms-button:disabled { opacity:.5; cursor:not-allowed; }
#msix-builder-concept .ms-button.full { width:100%; margin-top:14px; }
#msix-builder-concept .ms-footer { display:flex; gap:12px; justify-content:space-between; align-items:center; margin-top:24px; flex-wrap:wrap; }
#msix-builder-concept .ms-actions { display:flex; gap:8px; flex-wrap:wrap; }
#msix-builder-concept .ms-status { font-size:12px; color:var(--ms-muted); }
#msix-builder-concept .ms-check { display:flex; gap:10px; align-items:flex-start; margin:13px 0; }
#msix-builder-concept input[type="checkbox"] { width:17px; height:17px; accent-color:var(--ms-accent); margin:3px 0 0; flex:none; }
#msix-builder-concept .ms-check small { display:block; font-size:12px; color:var(--ms-muted); margin-top:3px; }
#msix-builder-concept .ms-toolbar { display:flex; justify-content:space-between; gap:10px; align-items:center; margin-bottom:16px; flex-wrap:wrap; }
#msix-builder-concept .ms-toolbar h3 { margin:0; }
#msix-builder-concept .ms-file { display:flex; align-items:center; gap:11px; padding:13px 0; border-bottom:1px solid var(--ms-line); min-width:0; }
#msix-builder-concept .ms-file:last-child { border-bottom:0; }
#msix-builder-concept .ms-file .ms-filename { flex:1; min-width:0; overflow-wrap:anywhere; }
#msix-builder-concept .ms-file small { display:block; color:var(--ms-muted); font-size:11px; }
#msix-builder-concept .ms-file > span:last-child { font-size:12px; color:var(--ms-muted); white-space:nowrap; }
#msix-builder-concept .ms-file svg { color:var(--ms-muted); flex:none; }
#msix-builder-concept .ms-choice { display:flex; align-items:center; gap:10px; padding:14px; border:1px solid var(--ms-line); border-radius:7px; margin-bottom:10px; }
#msix-builder-concept .ms-choice.active { border-color:var(--ms-accent); background:var(--ms-soft); }
#msix-builder-concept .ms-choice strong { display:block; font-size:13px; font-weight:600; }
#msix-builder-concept .ms-choice small { color:var(--ms-muted); font-size:12px; }
#msix-builder-concept .ms-good { color:var(--ms-good); }
#msix-builder-concept .ms-warn { color:var(--ms-warn); }
#msix-builder-concept .ms-error { color:var(--ms-error); }
#msix-builder-concept .ms-alert { padding:13px 15px; border:1px solid var(--ms-line); border-left:3px solid var(--ms-error); background:var(--ms-paper); border-radius:6px; margin-bottom:17px; font-size:12px; }
#msix-builder-concept .ms-alert button { margin-top:10px; }
#msix-builder-concept .ms-stage { display:flex; align-items:flex-start; gap:12px; padding:14px 0; border-bottom:1px solid var(--ms-line); }
#msix-builder-concept .ms-stage:last-child { border:0; }
#msix-builder-concept .ms-stage .ms-stage-title { flex:1; min-width:0; }
#msix-builder-concept .ms-stage small { display:block; color:var(--ms-muted); font-size:12px; }
#msix-builder-concept .ms-stage > span:last-child { font-size:12px; }
#msix-builder-concept .ms-meter { height:6px; border-radius:4px; background:var(--ms-line); overflow:hidden; margin:12px 0; }
#msix-builder-concept .ms-meter div { height:100%; background:var(--ms-accent); }
#msix-builder-concept .ms-code { font:12px/1.8 Consolas,monospace; white-space:pre-wrap; overflow-wrap:anywhere; color:var(--ms-muted); margin:0; }
#msix-builder-concept .ms-bottom { display:flex; gap:10px; justify-content:space-between; padding:11px 22px; border-top:1px solid var(--ms-line); background:var(--ms-paper); flex-wrap:wrap; font-size:11px; color:var(--ms-muted); }
#msix-builder-concept .ms-steps { display:grid; grid-template-columns:repeat(6,1fr); gap:8px; padding:22px 26px 0; }
#msix-builder-concept .ms-steps button { border:0; background:transparent; padding:8px 0; display:flex; flex-direction:column; align-items:center; gap:7px; color:var(--ms-muted); font-size:12px; }
#msix-builder-concept .ms-step-dot { width:27px; height:27px; border:1px solid var(--ms-line); border-radius:50%; display:grid; place-items:center; background:var(--ms-paper); font-size:12px; }
#msix-builder-concept .ms-steps button[aria-current="step"] { color:var(--ms-accent); }
#msix-builder-concept .ms-steps button[aria-current="step"] .ms-step-dot { background:var(--ms-button); color:var(--ms-onbutton); border-color:transparent; }
#msix-builder-concept .ms-guided .ms-content { flex:1; padding:24px 40px; }
#msix-builder-concept .ms-guided .ms-cols { grid-template-columns:minmax(0,1.6fr) minmax(210px,1fr); }
#msix-builder-concept .ms-guided { --ms-accent:light-dark(#146d60,#82d7c6); --ms-soft:light-dark(#eaf5f2,#263e3a); --ms-button:light-dark(#146d60,#9ae1d2); --ms-onbutton:light-dark(#ffffff,#14332e); }
@media(max-width:800px) { #msix-builder-concept .ms-layout { grid-template-columns:154px minmax(0,1fr); } #msix-builder-concept .ms-content { padding:22px 18px; } #msix-builder-concept .ms-cols, #msix-builder-concept .ms-guided .ms-cols { grid-template-columns:1fr; } #msix-builder-concept .ms-side { display:grid; grid-template-columns:1fr 1fr; gap:16px; } #msix-builder-concept .ms-side > .ms-caption { grid-column:1/-1; } #msix-builder-concept .ms-guided .ms-content { padding:22px; } }
@media(max-width:520px) { #msix-builder-concept .ms-app { min-height:1900px; } #msix-builder-concept .ms-layout { grid-template-columns:1fr; } #msix-builder-concept .ms-nav { border-right:0; border-bottom:1px solid var(--ms-line); padding:12px; display:grid; grid-template-columns:1fr 1fr; gap:3px; } #msix-builder-concept .ms-project, #msix-builder-concept .ms-nav-end { display:none; } #msix-builder-concept .ms-nav button { padding:8px; margin:0; font-size:12px; } #msix-builder-concept .ms-fields, #msix-builder-concept .ms-side { grid-template-columns:1fr; } #msix-builder-concept .ms-top { padding:14px 16px; padding-right:96px; } #msix-builder-concept .ms-top .ms-caption { display:none; } #msix-builder-concept .ms-content, #msix-builder-concept .ms-guided .ms-content { padding:20px 14px; } #msix-builder-concept .ms-panel { padding:16px; } #msix-builder-concept .ms-steps { padding:12px; grid-template-columns:repeat(3,1fr); } #msix-builder-concept .ms-footer { align-items:stretch; } #msix-builder-concept .ms-actions { flex:1; justify-content:flex-end; } #msix-builder-concept .ms-bottom { padding:10px 14px; } }
@media(pointer:coarse) { #msix-builder-concept .ms-button, #msix-builder-concept .ms-nav button, #msix-builder-concept .ms-steps button { min-height:44px; } #msix-builder-concept input:not([type="checkbox"]), #msix-builder-concept select { font-size:16px; } }
</style>
<div class="viz-carousel" aria-label="MSIX 제작기 디자인" data-previous-label="이전 시안" data-next-label="다음 시안">
<section data-variant="프로젝트 작업실" aria-label="프로젝트 작업실">
<div class="ms-app" data-app="studio"></div>
</section>
<section data-variant="단계별 제작" aria-label="단계별 제작" hidden>
<div class="ms-app ms-guided" data-app="guided"></div>
</section>
</div>
<script>
(() => {
const root = document.getElementById('msix-builder-concept');
const pages = [['app','앱 정보','panels-top-left'],['files','파일 구성','folder-open'],['manifest','패키지 설정','package'],['sign','서명','shield-check'],['update','업데이트','refresh-cw'],['build','검증 · 빌드','hammer']];
const defaults = () => ({page:'app',name:'Orbit Notes',description:'팀의 아이디어를 한곳에',publisherName:'Example Studio',identity:'ExampleStudio.OrbitNotes',publisher:'CN=Example Studio',version:'1.2.0.0',arch:'x64',exe:'OrbitNotes.exe',minOs:'10.0.19041.0',sign:'development',certSubject:'CN=Example Studio',autoUpdate:false,url:'https://downloads.example.com/orbit/stable/OrbitNotes.appinstaller',interval:'24',prompt:true,extraFile:false,revision:1,validated:false,build:'idle',log:false,message:'',error:''});
const state = {studio:defaults(),guided:defaults()};
const sessionId = Date.now().toString(36) + Math.random().toString(36).slice(2);
const esc = v => String(v).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const icon = name => `<i data-lucide="${name}" aria-hidden="true"></i>`;
const field = (s,key,label,full=false,hint='') => `<label class="ms-field ${full?'ms-full':''}">${label}<input data-field="${key}" value="${esc(s[key])}" ${key==='url'?'type="url"':'type="text"'}>${hint?`<small>${hint}</small>`:''}</label>`;
const select = (s,key,label,opts) => `<label class="ms-field">${label}<select data-field="${key}">${opts.map(([v,l])=>`<option value="${v}" ${s[key]===v?'selected':''}>${l}</option>`).join('')}</select></label>`;
const check = (s,key,label,hint='') => `<label class="ms-check"><input type="checkbox" data-field="${key}" ${s[key]?'checked':''}><span>${label}${hint?`<small>${hint}</small>`:''}</span></label>`;
const kv=(a,b)=>`<div class="ms-kv"><span>${a}</span><span>${b}</span></div>`;
function validate(s) {
if(!s.name.trim()||!s.identity.trim()||!s.publisher.trim()) return ['app','제품 이름과 패키지 식별 정보를 입력해 주세요.'];
if(!/^\d+\.\d+\.\d+\.\d+$/.test(s.version)||s.version.split('.').some(n=>Number(n)>65535)||Number(s.version.split('.')[0])===0) return ['app','버전을 1.2.0.0처럼 네 부분 숫자로 입력해 주세요.'];
if(!['OrbitNotes.exe'].includes(s.exe)) return ['files','실행 파일이 패키지 목록에 없습니다. OrbitNotes.exe를 지정해 주세요.'];
if(s.sign!=='none' && s.publisher!==s.certSubject) return ['sign','Publisher와 인증서 Subject가 일치하지 않습니다.'];
if(s.autoUpdate) { try { const u=new URL(s.url); if(u.protocol!=='https:'||!u.pathname.endsWith('.appinstaller')) throw Error(); } catch { return ['update','HTTPS 주소와 .appinstaller 파일 이름을 확인해 주세요.']; } }
return null;
}
function restore(saved) {
if(!saved||saved.schema!==1)return;
['studio','guided'].forEach(k=>{
const d=defaults(), v=saved[k];if(!v||typeof v!=='object')return;
Object.keys(d).forEach(f=>{if(typeof v[f]===typeof d[f]&&(!['string'].includes(typeof v[f])||v[f].length<1000))d[f]=v[f];});
if(!pages.some(p=>p[0]===d.page))d.page='app';
if(!['development','production','none'].includes(d.sign))d.sign='development';
d.validated=false;d.build='idle';d.error='';d.message='';state[k]=d;
});
}
restore(window.openai?.widgetState?.privateContent);
function persist(){ const host=window.openai;if(host?.setWidgetState)host.setWidgetState({modelContent:{design:'MSIX-first builder',studioPage:state.studio.page,guidedPage:state.guided.page},privateContent:{schema:1,sessionId,...state}}).catch(()=>{}); }
function preview(s) {
return `<aside class="ms-side"><div class="ms-panel"><h3>앱 표시 미리보기</h3><div class="ms-appicon">${icon('notebook-pen')}</div><div class="ms-preview-name">${esc(s.name||'앱 이름')}</div><p class="ms-preview-description">${esc(s.description)}</p><div class="ms-line"></div>${kv('게시자',esc(s.publisherName))}${kv('버전',esc(s.version))}${kv('아키텍처',esc(s.arch))}${kv('설치 위치','Windows에서 관리')}<div class="ms-note">설치 폴더 선택 없이 패키지로 배포합니다.</div></div><p class="ms-caption" style="margin-top:12px">표시 정보 예시입니다. 실제 App Installer 화면은 Windows 환경에 따라 달라집니다.</p></aside>`;
}
function side(s,title,body){return `<aside class="ms-side"><div class="ms-panel"><h3>${title}</h3>${body}</div></aside>`;}
function pane(s) {
if(s.page==='app')return `<div class="ms-cols"><div class="ms-panel"><h3>사용자에게 표시할 정보</h3><div class="ms-fields">${field(s,'name','앱 이름',true)}${field(s,'description','설명',true)}${field(s,'publisherName','게시자 표시명',true)}${field(s,'version','패키지 버전',false,'네 부분 숫자 버전')}${select(s,'arch','아키텍처',[['x64','x64'],['arm64','ARM64']])}</div><div class="ms-line"></div><div class="ms-toolbar"><span>앱 아이콘</span><span class="ms-caption">Assets / Logo.png · 예시</span></div><p class="ms-caption">실제 제품에서는 로고 파일과 크기를 검사합니다.</p></div>${preview(s)}</div>`;
if(s.page==='files')return `<div class="ms-cols"><div class="ms-panel"><div class="ms-toolbar"><h3>패키지에 포함할 파일</h3><button class="ms-button cursor-interaction" data-action="add-file">${icon(s.extraFile?'minus':'plus')}${s.extraFile?'예제 파일 제거':'예제 파일 추가'}</button></div><p class="ms-caption">소스 폴더 · D:\\releases\\orbit-notes</p>${[['OrbitNotes.exe','앱 실행 파일','18.4 MB'],['Orbit.Core.dll','애플리케이션 라이브러리','6.2 MB'],['Assets/Logo.png','앱 이미지 · 예시','24 KB'],['Resources/ko-KR.json','한국어 리소스','12 KB'],...(s.extraFile?[['Resources/en-US.json','추가한 예제 파일','10 KB']]:[])].map(([n,d,z])=>`<div class="ms-file">${icon(n.endsWith('exe')?'file-code':'file')}<div class="ms-filename">${esc(n)}<small>${d}</small></div><span>${z}</span></div>`).join('')}<div class="ms-line"></div>${field(s,'exe','실행 파일',true,'패키지 내부 상대 경로')}</div>${side(s,'파일 구성 원칙',`${kv('포함 파일',`${s.extraFile?5:4}개 · 예시`)}${kv('배치 위치','패키지 내부')}${kv('원본 파일','변경하지 않음')}<div class="ms-note">문서·설정·로그는 앱 데이터 위치를 사용합니다. 설치 폴더 쓰기에 의존하는 앱은 호환성을 확인해야 합니다.</div>`)}</div>`;
if(s.page==='manifest')return `<div class="ms-cols"><div class="ms-panel"><h3>패키지 식별 정보</h3><div class="ms-fields">${field(s,'identity','패키지 이름 · Identity Name',true)}${field(s,'publisher','Publisher',true,'서명 인증서의 Subject와 일치해야 합니다.')}${select(s,'minOs','최소 Windows 버전',[['10.0.19041.0','Windows 10 · 2004 이상'],['10.0.22000.0','Windows 11 이상']])}</div><div class="ms-line"></div><h3>앱 실행 방식</h3><div class="ms-choice active">${icon('monitor')}<div><strong>일반 데스크톱 앱</strong><small>Windows.FullTrustApplication</small></div></div><p class="ms-caption">runFullTrust 선언을 포함합니다. 서비스·드라이버·임의 설치 작업은 초기 지원 범위에 포함하지 않습니다.</p></div>${side(s,'생성할 manifest',`<pre class="ms-code">&lt;Identity\n Name="${esc(s.identity)}"\n Publisher="${esc(s.publisher)}"\n Version="${esc(s.version)}"\n ProcessorArchitecture="${esc(s.arch)}" /&gt;</pre><div class="ms-note">미리보기는 요약입니다. 실제 생성 시 SDK 스키마·자산·의존성을 함께 검사합니다.</div>`)}</div>`;
if(s.page==='sign')return `<div class="ms-cols"><div class="ms-panel"><h3>서명 프로필</h3>${select(s,'sign','사용할 프로필',[['development','개발용 인증서 · 예시'],['production','운영 서명 프로필 · 예시'],['none','미서명 개발 빌드']])}<div class="ms-line"></div>${s.sign==='none'?'<p class="ms-warn">미서명 패키지는 직접 배포 준비 상태가 될 수 없습니다.</p>':`${field(s,'certSubject','인증서 Subject · 예시',true)}<div class="ms-line"></div>${kv('패키지 Publisher',esc(s.publisher))}${kv('이름 일치',s.publisher===s.certSubject?'<span class="ms-good">일치</span>':'<span class="ms-error">불일치</span>')}`}<div class="ms-note">개인 키나 인증서 암호를 프로젝트에 저장하지 않습니다. 실제 제품에서는 OS 또는 서명 서비스 프로필을 참조합니다.</div></div>${side(s,'배포 전 확인',`<div class="ms-stage">${icon('shield-check')}<div class="ms-stage-title">패키지 서명<small>서명자·체인·타임스탬프 검사</small></div></div><div class="ms-stage">${icon('monitor-check')}<div class="ms-stage-title">대상 PC 신뢰<small>개발용 인증서 성공과 구분</small></div></div><div class="ms-note">이 시안은 인증서 저장소에 접근하거나 서명하지 않습니다.</div>`)}</div>`;
if(s.page==='update')return `<div class="ms-cols"><div class="ms-panel"><h3>직접 배포</h3><div class="ms-choice active">${icon('globe')}<div><strong>내 서버에서 배포</strong><small>Store 계정 없이 MSIX를 전달합니다.</small></div></div>${check(s,'autoUpdate','App Installer 업데이트 사용','MSIX와 함께 .appinstaller 파일을 생성합니다.')}${s.autoUpdate?`<div class="ms-line"></div><div class="ms-fields">${field(s,'url','업데이트 안내 파일 주소',true,'패키지 파일은 버전별 고정 URL을 사용합니다.')}${select(s,'interval','실행 시 확인 간격',[['0','앱 실행마다'],['24','24시간'],['48','48시간']])}</div>${check(s,'prompt','업데이트 안내 표시')}`:'<div class="ms-note">MSIX 파일을 직접 전달합니다. 자동 업데이트 경로는 연결되지 않습니다.</div>'}</div>${side(s,'생성할 결과',`${kv('설치 패키지','.msix')}${kv('업데이트 안내',s.autoUpdate?'.appinstaller':'사용 안 함')}${kv('업데이트 적용','Windows 배포 기능')}<div class="ms-note">새 버전 전체 패키지를 생성합니다. Windows의 차등 업데이트를 이용하며 별도 MSP 파일을 만들지 않습니다.</div><p class="ms-caption" style="margin-top:14px">Store 등록·자동 업로드·그룹 배포 서버는 후속 단계입니다.</p>`)}</div>`;
const completed=s.build==='done',failed=s.build==='failed',ready=s.validated;
const stages=[['설정과 파일 검사','manifest · 입력 · 경로','list-checks'],['MSIX 패키징','Windows SDK · MakeAppx','package'],['서명과 무결성',s.sign==='none'?'미서명 개발 결과':'서명 프로필 · 해시','shield-check'],['결과 보관','불변 아티팩트 · 검증 보고서','archive']];
return `<div class="ms-cols"><div class="ms-panel"><div class="ms-toolbar"><h3>빌드 계획</h3><span class="ms-pill">${completed?'모의 빌드 완료':failed?'검증 오류':ready?'모의 검사 통과':'검사 대기'}</span></div>${stages.map(([t,d,i],n)=>`<div class="ms-stage">${icon(completed?'circle-check':i)}<div class="ms-stage-title">${t}<small>${d}</small></div><span class="${completed?'ms-good':'ms-caption'}">${completed?(s.sign==='none'&&n===2?'서명 생략':'모의 완료'):'대기'}</span></div>`).join('')}<div class="ms-meter" role="progressbar" aria-label="모의 빌드 진행" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${completed?100:0}"><div style="width:${completed?100:0}%"></div></div><div class="ms-actions"><button class="ms-button cursor-interaction" data-action="validate">설정 검사</button><button class="ms-button primary cursor-interaction" data-action="build">${icon('play')}모의 빌드</button></div>${completed?`<div class="ms-line"></div><h3>결과 예시</h3><pre class="ms-code">OrbitNotes_${esc(s.version)}_${esc(s.arch)}.msix\n${s.autoUpdate?'OrbitNotes.appinstaller\n':''}release.json\nvalidation-report.json</pre><button class="ms-button cursor-interaction" data-action="log" style="margin-top:14px">${s.log?'기록 닫기':'모의 기록 보기'}</button>${s.log?'<pre class="ms-code" style="margin-top:12px">[예시] 입력 스냅샷 고정\n[예시] MakeAppx 단계 완료\n[예시] 서명 정책 적용\n[예시] 결과 목록 생성\n실제 명령 실행 및 파일 생성 없음</pre>':''}`:''}</div>${side(s,'릴리스 준비 상태',`${kv('패키지 생성',completed?'모의 완료':'대기')}${kv('서명',s.sign==='none'?'생략':completed?'모의 완료':'대기')}${kv('설치·실행 시험','미실행')}${kv('업데이트·제거 시험','미실행')}<div class="ms-note">${s.sign==='development'?'개발용 인증서로 설정되어 있습니다. 실제 사용자 배포에는 대상 PC 신뢰와 설치 시험이 필요합니다.':'빌드와 배포 준비는 별도 상태입니다. 실제 VM 검증 증빙이 필요합니다.'}</div>`)}</div>`;
}
const descriptions={app:'앱 이름과 버전을 입력하면 패키지 표시 정보를 미리 확인할 수 있습니다.',files:'배포할 파일을 모으고 앱의 실행 파일을 지정합니다.',manifest:'Windows가 앱을 식별할 이름과 실행 조건을 설정합니다.',sign:'패키지 게시자와 서명 프로필을 연결합니다.',update:'MSIX만 전달하거나, 내 서버를 통한 업데이트 경로를 연결합니다.',build:'생성 전 설정을 검사하고 패키지와 검증 결과를 확인합니다.'};
function render(k) {
const app=root.querySelector(`[data-app="${k}"]`),s=state[k],idx=pages.findIndex(p=>p[0]===s.page);
const nav=pages.map(([id,label,ic],n)=>`<button class="cursor-interaction" data-page="${id}" ${s.page===id?'aria-current="step"':''}>${k==='guided'?`<span class="ms-step-dot">${n+1}</span>`:icon(ic)}<span>${label}</span></button>`).join('');
const error=s.error?`<div class="ms-alert" role="alert">${esc(s.error)}${s.errorPage?`<br><button class="ms-button cursor-interaction" data-page="${esc(s.errorPage)}">해당 설정으로 이동</button>`:''}</div>`:'';
app.innerHTML=`<header class="ms-top"><div class="ms-brand"><span class="ms-mark">${icon('package')}</span>MSIX Studio</div><span class="ms-pill">디자인 시안</span><span class="ms-spacer"></span><span class="ms-caption">직접 배포용 패키지 제작</span></header>${k==='guided'?`<nav class="ms-steps" aria-label="제작 단계">${nav}</nav>`:'<div class="ms-layout">'+`<nav class="ms-nav" aria-label="프로젝트 설정"><div class="ms-project"><span class="ms-caption">현재 프로젝트</span><strong>${esc(s.name)}</strong><span class="ms-pill">MSIX · ${esc(s.arch)}</span></div>${nav}<div class="ms-nav-end ms-caption">Windows 패키지 제작<br>단일 제품 · 단일 아키텍처</div></nav>`}<main class="ms-content"><div class="ms-eyebrow">${k==='guided'?`STEP ${idx+1} OF 6`:'PACKAGE WORKSPACE'}</div><h2>${pages[idx][1]}</h2><p class="ms-lead">${descriptions[s.page]}</p>${error}${pane(s)}<footer class="ms-footer"><div class="ms-status" role="status" aria-live="polite">${esc(s.message||'예제 데이터 · 실제 파일 접근 없음')}</div><div class="ms-actions">${idx>0?'<button class="ms-button cursor-interaction" data-action="prev">이전</button>':''}${idx<5?'<button class="ms-button primary cursor-interaction" data-action="next">다음 단계 '+icon('arrow-right')+'</button>':''}</div></footer></main>${k==='studio'?'</div>':''}<div class="ms-bottom"><span>프로젝트 변경 ${s.revision} · ${esc(s.version)}</span><span>실제 패키지 생성·서명·설치 없음</span></div>`;
if(globalThis.lucide)globalThis.lucide.createIcons({attrs:{width:16,height:16}});
}
function runValidation(s){const err=validate(s);s.error=err?err[1]:'';s.errorPage=err?err[0]:'';s.validated=!err;s.message=err?'설정을 수정한 후 다시 검사해 주세요.':'모의 설정 검사 통과 · SDK와 실제 파일 검사는 미실행';return !err;}
root.addEventListener('click',e=>{
const b=e.target.closest('button'),app=e.target.closest('[data-app]');if(!b||!app)return;const k=app.dataset.app,s=state[k];
if(b.dataset.page){s.page=b.dataset.page;s.error='';s.errorPage='';}
const a=b.dataset.action,idx=pages.findIndex(p=>p[0]===s.page);
if(a==='next'||a==='prev'){s.page=pages[Math.max(0,Math.min(5,idx+(a==='next'?1:-1)))][0];s.error='';s.errorPage='';}
if(a==='add-file'){s.extraFile=!s.extraFile;s.revision++;s.validated=false;s.build='idle';s.message=s.extraFile?'예제 파일을 추가했습니다.':'추가한 예제 파일을 제거했습니다.';}
if(a==='validate')runValidation(s);
if(a==='build'){s.build=runValidation(s)?'done':'failed';if(s.build==='done')s.message='모의 빌드 완료 · 실제 산출물과 설치 증빙은 없습니다.';}
if(a==='log')s.log=!s.log;
render(k);persist();
});
root.addEventListener('change',e=>{
const f=e.target.dataset.field,app=e.target.closest('[data-app]');if(!f||!app)return;const k=app.dataset.app,s=state[k];s[f]=e.target.type==='checkbox'?e.target.checked:e.target.value;s.revision++;s.validated=false;s.build='idle';s.error='';s.errorPage='';s.message='설정이 변경되었습니다. 빌드 전 다시 검사합니다.';render(k);persist();
});
window.addEventListener('openai:set_globals',e=>{const saved=e.detail?.globals?.widgetState?.privateContent;if(!saved||saved.schema!==1||saved.sessionId===sessionId)return;restore(saved);render('studio');render('guided');});
render('studio');render('guided');
if(globalThis.Tweak){['studio','guided'].forEach(k=>{const app=root.querySelector(`[data-app="${k}"]`),design={corners:12};const tweak=new Tweak({container:app,onChange:()=>{app.style.borderRadius=design.corners+'px';}});tweak.addSlider(design,'corners',{label:'창 모서리',min:0,max:20,step:2,unit:'px'});});}
})();
</script>
</div>
File diff suppressed because it is too large. Load diff