Caterium v17.6.9 — offer PDF workspace and gallery uploads

* Add v17.6.9 client offer workspace tabs

* Persist two editable offer gallery photos

* Add one-time v17.6.9 release preparation workflow

* Prepare Caterium v17.6.9 offer PDF workspace

* Remove one-time v17.6.9 release workflow

* Fix offer workspace mutation observer loop

* Add one-time v17.6.9 idempotency hotfix workflow

* Make v17.6.9 offer workspace rendering idempotent

* Remove one-time v17.6.9 idempotency workflow

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
pavlov346346-source 2026-09-08 08:21:11 +03:00 committed by GitHub
parent 4ba730990b
commit fbafb33a33
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 204 additions and 29 deletions

View File

@ -1,6 +1,6 @@
{ {
"app": "Caterium", "app": "Caterium",
"version": "v17.6.8", "version": "v17.6.9",
"channel": "production", "channel": "production",
"schema": 17, "schema": 17,
"legacyStateKept": true, "legacyStateKept": true,
@ -11,7 +11,7 @@
"serverReady": true, "serverReady": true,
"workspaceAutoDiscovery": true, "workspaceAutoDiscovery": true,
"invitesTemporarilyDisabled": false, "invitesTemporarilyDisabled": false,
"pwaCache": "v73-20260908-v17-6-8-developer-console-ux", "pwaCache": "v74-20260908-v17-6-9-offer-pdf-tabs-gallery",
"fullOfferDescriptions": true, "fullOfferDescriptions": true,
"dynamicOfferRows": true, "dynamicOfferRows": true,
"pdfOfferDescriptionFix": true, "pdfOfferDescriptionFix": true,
@ -108,7 +108,7 @@
"catalogCompositionTildaEndpoint": "getproduct", "catalogCompositionTildaEndpoint": "getproduct",
"catalogCompositionPremiumForceRefresh": true, "catalogCompositionPremiumForceRefresh": true,
"catalogCompositionCacheRequiresPremium": true, "catalogCompositionCacheRequiresPremium": true,
"notes": "Developer Console UX: fixed async DOM race errors, stable company numbering and deletion, accounts grouped by company, compact plan matrix, grouped production error journal.", "notes": "Client offer workspace: PDF preview, editor and PDF templates separated into tabs; editor scroll restored; two per-offer final gallery photos can be uploaded and are used in preview/PDF.",
"catalogPhotoSources": 113, "catalogPhotoSources": 113,
"catalogPhotosStoredInCatalog": true, "catalogPhotosStoredInCatalog": true,
"catalogLegacyPhotosInCatalog": 60, "catalogLegacyPhotosInCatalog": 60,
@ -246,7 +246,7 @@
"signupTrial": "14-day Full", "signupTrial": "14-day Full",
"developerMfaInputSelectorFixed": true, "developerMfaInputSelectorFixed": true,
"developerMfaRepeatedValidationToastsFixed": true, "developerMfaRepeatedValidationToastsFixed": true,
"release": "20260908-v17-6-8-developer-console-ux", "release": "20260908-v17-6-9-offer-pdf-tabs-gallery",
"registrationFlow": "email-password-confirm-company-auto-login", "registrationFlow": "email-password-confirm-company-auto-login",
"emailConfirmationRequired": false, "emailConfirmationRequired": false,
"employeeInviteLinks": false, "employeeInviteLinks": false,
@ -353,5 +353,10 @@
"developerJournalGroupedErrors": true, "developerJournalGroupedErrors": true,
"developerJournalFiltersLocalCopies": true, "developerJournalFiltersLocalCopies": true,
"developerDomRaceFix": true, "developerDomRaceFix": true,
"developerConsoleUXModule": "core/developer-console-v1768.js" "developerConsoleUXModule": "core/developer-console-v1768.js",
"offerWorkspaceTabs": true,
"offerTemplatesSeparateTab": true,
"offerEditorScrollable": true,
"offerTwoCustomGalleryPhotos": true,
"offerWorkspaceModule": "core/offer-workspace-v1769.js"
} }

View File

@ -0,0 +1,8 @@
Caterium v17.6.9 — Client Offer PDF Workspace
- Оформление PDF вынесено из верхней части предложения в отдельную вкладку «Оформление PDF».
- Вкладка «PDF и предпросмотр» показывает только страницы предложения и кнопку скачивания, без редактора и длинной сетки шаблонов.
- «Редактор» получил собственную прокручиваемую область, поэтому длинные настройки больше не блокируют скролл.
- В редактор добавлены два независимых поля загрузки финальных фотографий «Как это выглядит на вашем столе».
- Фотографии оптимизируются в браузере, сохраняются отдельно для предложения и подставляются в предпросмотр и скачиваемый PDF.
- Кнопка «Сбросить» возвращает стандартную фотографию для каждого слота.

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "caterium-app", "name": "caterium-app",
"version": "17.6.8", "version": "17.6.9",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "caterium-app", "name": "caterium-app",
"version": "17.6.8", "version": "17.6.9",
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.51.0", "@playwright/test": "^1.51.0",
"http-server": "^14.1.1", "http-server": "^14.1.1",

View File

@ -1,10 +1,10 @@
{ {
"name": "caterium-app", "name": "caterium-app",
"private": true, "private": true,
"version": "17.6.8", "version": "17.6.9",
"type": "module", "type": "module",
"scripts": { "scripts": {
"check:syntax": "node --check public/app-runtime.js && node --check public/service-worker.js && node --check public/legacy/bootstrap.js && node --check public/core/sun-safe.js && node --check public/core/performance.js && node --check public/core/hotfix-v1763.js && node --check public/core/ops-ux-v1762.js && node --check public/core/ux-fixes-v1764.js && node --check public/core/pdf-engine.js && node --check public/core/classic-offer-pdf-v1767.js && node --check public/core/developer-console-v1768.js", "check:syntax": "node --check public/app-runtime.js && node --check public/service-worker.js && node --check public/legacy/bootstrap.js && node --check public/core/sun-safe.js && node --check public/core/performance.js && node --check public/core/hotfix-v1763.js && node --check public/core/ops-ux-v1762.js && node --check public/core/ux-fixes-v1764.js && node --check public/core/pdf-engine.js && node --check public/core/classic-offer-pdf-v1767.js && node --check public/core/developer-console-v1768.js && node --check public/core/offer-workspace-v1769.js",
"test:static": "node tests/static-security.mjs", "test:static": "node tests/static-security.mjs",
"check:release": "node tests/release-check.mjs", "check:release": "node tests/release-check.mjs",
"check:deploy": "npm run check:syntax && npm run test:static && npm run check:release", "check:deploy": "npm run check:syntax && npm run test:static && npm run check:release",

View File

@ -0,0 +1,136 @@
(()=>{
'use strict';
if(window.SunOfferWorkspaceV1769)return;
const VERSION='17.6.9';
const RELEASE='20260908-v17-6-9-offer-pdf-tabs-gallery';
const MODES=new Set(['preview','editor','templates']);
const GALLERY_KEY='sunOfferGalleryOverridesV1769';
const DEFAULT_GALLERY=['offer-gallery/001.jpg','offer-gallery/002.jpg'];
const $=id=>document.getElementById(id);
const modal=()=>$('sunClientOfferModal');
const dialog=()=>modal()?.querySelector('.dialog')||null;
const toast=(text,type='info',ms=4200)=>{try{return window.SunEnterprise?.toast?.(text,type,ms)}catch(_){};};
let mode='preview',activeOrderId='';
let openersPatched=false,classicPatched=false;
function readGalleryMap(){try{const v=JSON.parse(localStorage.getItem(GALLERY_KEY)||'{}');return v&&typeof v==='object'&&!Array.isArray(v)?v:{}}catch(_){return{}}}
function writeGalleryMap(map){try{localStorage.setItem(GALLERY_KEY,JSON.stringify(map));window.dispatchEvent(new CustomEvent('sun:offer-gallery-v1769-change',{detail:{orderId:activeOrderId}}));return true}catch(e){console.error('[Caterium v17.6.9] gallery persist failed',e);toast('Не удалось сохранить фото: хранилище заполнено.','error',6000);return false}}
function galleryFor(orderId=activeOrderId){const map=readGalleryMap(),v=map[String(orderId||'')];return Array.isArray(v)&&v.length?v.slice(0,2).map((x,i)=>x||DEFAULT_GALLERY[i]):DEFAULT_GALLERY.slice()}
function saveGallerySlot(index,value){if(!activeOrderId){toast('Сначала сохраните заказ, чтобы закрепить фотографии предложения.','warn',5200);return false}const map=readGalleryMap(),key=String(activeOrderId),arr=Array.isArray(map[key])?map[key].slice(0,2):DEFAULT_GALLERY.slice();while(arr.length<2)arr.push(DEFAULT_GALLERY[arr.length]);arr[index]=value||DEFAULT_GALLERY[index];map[key]=arr;const ok=writeGalleryMap(map);if(ok){syncGalleryUi();refreshOffer();}return ok}
function currentDraftId(){try{if(window.draft?.id!=null)return String(window.draft.id)}catch(_){}const raw=document.querySelector('#orderForm [name="id"],#orderId,#order-id')?.value;return raw?String(raw):''}
function installStyle(){
if($('sunOfferWorkspaceV1769Style'))return;
const s=document.createElement('style');s.id='sunOfferWorkspaceV1769Style';s.textContent=`
#sunClientOfferModal .dialog.sun-offer-workspace-v1769{width:min(1240px,calc(100vw - 24px));height:min(94vh,980px);max-height:94vh;overflow:hidden!important;padding:0!important;display:flex;flex-direction:column;background:#f5f6f5;border-radius:19px}
#sunClientOfferModal .sun-offer-modal-head{flex:0 0 auto;padding:14px 16px;background:#fff;border-bottom:1px solid #e5e7e6;position:relative;z-index:5}
.sun-offer-workspace-tabs-v1769{flex:0 0 auto;display:flex;align-items:center;gap:6px;padding:9px 12px;background:#fff;border-bottom:1px solid #e5e7e6;overflow-x:auto;scrollbar-width:thin}
.sun-offer-workspace-tabs-v1769 button{flex:0 0 auto;border:1px solid #dfe3e0;background:#f7f8f7;color:#48504a;border-radius:999px;padding:8px 13px;font-size:12px;font-weight:850;white-space:nowrap}
.sun-offer-workspace-tabs-v1769 button.on{background:#1d2a22;color:#fff;border-color:#1d2a22;box-shadow:0 4px 13px rgba(29,42,34,.16)}
#sunClientOfferModal .sun-offer-frame-wrap,#sunClientOfferModal #sunOfferEditor,#sunClientOfferModal #sunOfferTemplateV1764{flex:1 1 auto;min-height:0!important;overflow:auto!important;margin:0!important}
#sunClientOfferModal .sun-offer-frame-wrap{padding:14px 16px 20px;background:#eef0ee}
#sunClientOfferModal #sunOfferEditor{padding:15px 16px 24px!important;background:#fff;border:0!important}
#sunClientOfferModal #sunOfferTemplateV1764{padding:16px!important;background:#fff;border:0!important}
#sunClientOfferModal[data-offer-workspace-mode="preview"] #sunOfferEditor,#sunClientOfferModal[data-offer-workspace-mode="preview"] #sunOfferTemplateV1764{display:none!important}
#sunClientOfferModal[data-offer-workspace-mode="preview"] .sun-offer-frame-wrap{display:block!important}
#sunClientOfferModal[data-offer-workspace-mode="editor"] .sun-offer-frame-wrap,#sunClientOfferModal[data-offer-workspace-mode="editor"] #sunOfferTemplateV1764{display:none!important}
#sunClientOfferModal[data-offer-workspace-mode="editor"] #sunOfferEditor{display:block!important}
#sunClientOfferModal[data-offer-workspace-mode="templates"] .sun-offer-frame-wrap,#sunClientOfferModal[data-offer-workspace-mode="templates"] #sunOfferEditor{display:none!important}
#sunClientOfferModal[data-offer-workspace-mode="templates"] #sunOfferTemplateV1764{display:block!important}
#sunClientOfferModal #sunOfferTemplateV1764 .sun-v1764-offer-template-head{position:sticky;top:-16px;z-index:2;background:#fff;padding:1px 0 10px;margin-bottom:10px}
#sunClientOfferModal #sunOfferTemplateV1764 .sun-v1764-offer-template-grid{grid-template-columns:repeat(4,minmax(170px,1fr));gap:10px}
#sunClientOfferModal #sunOfferTemplateV1764 .sun-v1764-offer-template-grid button{min-height:166px;padding:10px;border-radius:15px}
#sunClientOfferModal #sunOfferTemplateV1764 .sun-offer-template-mini{height:116px}
#sunClientOfferModal .sun-offer-editor-gallery{border:1px solid #e0e4e1;border-radius:16px;padding:14px;background:#f8faf8}
#sunClientOfferModal .sun-offer-editor-gallery-images{display:grid!important;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px!important;margin-top:12px}
#sunClientOfferModal .sun-offer-gallery-upload-v1769{position:relative;border:1px solid #d9dfda;border-radius:14px;background:#fff;padding:9px;min-width:0}
#sunClientOfferModal .sun-offer-gallery-upload-v1769 img{display:block;width:100%!important;height:170px!important;object-fit:cover!important;border-radius:10px!important;background:#edf0ed}
#sunClientOfferModal .sun-offer-gallery-upload-v1769 .sun-offer-gallery-actions-v1769{display:flex;gap:7px;align-items:center;justify-content:space-between;margin-top:8px;flex-wrap:wrap}
#sunClientOfferModal .sun-offer-gallery-upload-v1769 .sun-offer-gallery-actions-v1769 b{font-size:12px;color:#303831}
#sunClientOfferModal .sun-offer-gallery-upload-v1769 input[type=file]{position:absolute;inline-size:1px;block-size:1px;opacity:0;pointer-events:none}
#sunClientOfferModal .sun-offer-gallery-upload-v1769 label,#sunClientOfferModal .sun-offer-gallery-upload-v1769 button{display:inline-flex!important;width:auto!important;flex-direction:row!important;align-items:center;gap:5px;margin:0!important;border-radius:9px;padding:7px 9px;font-size:11px;font-weight:800;cursor:pointer}
#sunClientOfferModal .sun-offer-gallery-upload-v1769 label{background:#1d2a22;color:#fff;border:1px solid #1d2a22}
#sunClientOfferModal .sun-offer-gallery-upload-v1769 button{background:#fff;color:#7b3a32;border:1px solid #e3c9c5}
#sunClientOfferModal .sun-offer-editor-note{margin:12px 0 0}
@media(max-width:820px){
#sunClientOfferModal{padding:0!important}#sunClientOfferModal .dialog.sun-offer-workspace-v1769{width:100vw;height:100vh;max-height:none;border-radius:0}
#sunClientOfferModal .sun-offer-modal-head{padding:10px 12px}#sunClientOfferModal .sun-offer-modal-actions{gap:5px}
#sunClientOfferModal .sun-offer-modal-actions button{padding:7px 9px;font-size:11px}
#sunClientOfferModal #sunOfferTemplateV1764 .sun-v1764-offer-template-grid{grid-template-columns:repeat(2,minmax(135px,1fr))}
#sunClientOfferModal .sun-offer-editor-gallery-images{grid-template-columns:1fr}
}
`;document.head.appendChild(s);
}
function ensureTabs(){
const d=dialog(),m=modal();if(!d||!m)return false;
d.classList.add('sun-offer-workspace-v1769');
let tabs=$('sunOfferWorkspaceTabsV1769');
if(!tabs){
tabs=document.createElement('nav');tabs.id='sunOfferWorkspaceTabsV1769';tabs.className='sun-offer-workspace-tabs-v1769';tabs.setAttribute('aria-label','Разделы предложения клиенту');tabs.innerHTML=`<button type="button" data-offer-workspace-tab="preview">PDF и предпросмотр</button><button type="button" data-offer-workspace-tab="editor">Редактор</button><button type="button" data-offer-workspace-tab="templates">Оформление PDF</button>`;
const head=d.querySelector('.sun-offer-modal-head');head?.insertAdjacentElement('afterend',tabs);
tabs.addEventListener('click',e=>{const b=e.target.closest('[data-offer-workspace-tab]');if(b)setMode(b.dataset.offerWorkspaceTab)});
}
setMode(mode,false);return true;
}
function setMode(next,focus=true){
if(!MODES.has(next))next='preview';mode=next;const m=modal();if(!m)return;
m.dataset.offerWorkspaceMode=mode;
document.querySelectorAll('#sunOfferWorkspaceTabsV1769 [data-offer-workspace-tab]').forEach(b=>b.classList.toggle('on',b.dataset.offerWorkspaceTab===mode));
const edit=$('sunOfferEdit');if(edit){const label=mode==='editor'?'К просмотру':'Редактировать';if(edit.textContent!==label)edit.textContent=label;edit.setAttribute('aria-pressed',mode==='editor'?'true':'false')}
if(focus){const target=mode==='editor'?$('sunOfferEditor'):mode==='templates'?$('sunOfferTemplateV1764'):m.querySelector('.sun-offer-frame-wrap');target?.scrollTo?.({top:0,behavior:'instant'});}
}
function syncGalleryUi(){const arr=galleryFor();for(let i=0;i<2;i++){const img=$(`sunOfferGalleryPreview${i}`);if(img)img.src=arr[i]||DEFAULT_GALLERY[i]}}
function upgradeGalleryUi(){
const wrap=modal()?.querySelector('.sun-offer-editor-gallery-images');if(!wrap)return false;
if(wrap.dataset.sunV1769==='1'){syncGalleryUi();return true;}
wrap.innerHTML=[0,1].map(i=>`<div class="sun-offer-gallery-upload-v1769"><img id="sunOfferGalleryPreview${i}" src="${DEFAULT_GALLERY[i]}" alt="Фото сервировки ${i+1}"><div class="sun-offer-gallery-actions-v1769"><b>Фото ${i+1}</b><div style="display:flex;gap:6px"><label>Загрузить<input type="file" accept="image/jpeg,image/png,image/webp" data-offer-gallery-slot="${i}"></label><button type="button" data-offer-gallery-reset="${i}">Сбросить</button></div></div></div>`).join('');
wrap.dataset.sunV1769='1';syncGalleryUi();return true;
}
async function fileToDataUrl(file){
const maxSide=1400,target=700*1024;
let bitmap;try{bitmap=await createImageBitmap(file)}catch(_){return await new Promise((res,rej)=>{const r=new FileReader();r.onload=()=>res(String(r.result||''));r.onerror=rej;r.readAsDataURL(file)})}
try{
let scale=Math.min(1,maxSide/Math.max(bitmap.width,bitmap.height)),quality=.86,best='';
for(let i=0;i<5;i++){
const c=document.createElement('canvas');c.width=Math.max(1,Math.round(bitmap.width*scale));c.height=Math.max(1,Math.round(bitmap.height*scale));const x=c.getContext('2d',{alpha:false});x.fillStyle='#fff';x.fillRect(0,0,c.width,c.height);x.drawImage(bitmap,0,0,c.width,c.height);const url=c.toDataURL('image/jpeg',quality);best=url;if(Math.round((url.length-22)*.75)<=target)break;quality=Math.max(.62,quality-.07);scale*=.86;
}
return best;
}finally{bitmap.close?.()}
}
function refreshOffer(){try{window.dispatchEvent(new CustomEvent('sunoffertemplatechange',{detail:{galleryOnly:true,orderId:activeOrderId}}))}catch(_){}}
function patchOpeners(){
if(openersPatched)return true;const one=window.sunOpenClientOffer,cur=window.sunOpenCurrentClientOffer;if(typeof one!=='function'||typeof cur!=='function')return false;
window.sunOpenClientOffer=function(id,...rest){activeOrderId=String(id??'');mode='preview';const out=one.call(this,id,...rest);setTimeout(()=>{enhance();setMode('preview',false);syncGalleryUi()},80);return out};
window.sunOpenCurrentClientOffer=function(...args){activeOrderId=currentDraftId();mode='preview';const out=cur.apply(this,args);setTimeout(()=>{enhance();setMode('preview',false);syncGalleryUi()},80);return out};
window.sunOpenClientOffer.__sunV1769=true;window.sunOpenCurrentClientOffer.__sunV1769=true;openersPatched=true;return true;
}
function patchClassic(){
if(classicPatched)return true;const api=window.SunClassicOfferPDFV1767;if(!api||typeof api.renderPages!=='function')return false;const original=api.renderPages.bind(api);
api.renderPages=async function(snapshot,...rest){const s=structuredClone(snapshot||{}),id=String(s.orderId||activeOrderId||'');if(id){s.finalGallery=galleryFor(id)}return original(s,...rest)};api.renderPages.__sunV1769=true;classicPatched=true;return true;
}
function enhance(){installStyle();ensureTabs();upgradeGalleryUi();patchOpeners();patchClassic();}
document.addEventListener('change',async e=>{
const input=e.target.closest?.('[data-offer-gallery-slot]');if(!input)return;const file=input.files?.[0];if(!file)return;if(!/^image\/(jpeg|png|webp)$/i.test(file.type||'')){toast('Поддерживаются JPG, PNG и WEBP.','warn');input.value='';return}const idx=Number(input.dataset.offerGallerySlot);input.disabled=true;toast(`Оптимизирую фото ${idx+1}`);try{const data=await fileToDataUrl(file);if(!data)throw new Error('Не удалось обработать изображение.');saveGallerySlot(idx,data);toast(`Фото ${idx+1} сохранено для этого предложения.`,'success')}catch(err){console.error(err);toast(err?.message||String(err),'error')}finally{input.disabled=false;input.value=''}
},true);
document.addEventListener('click',e=>{
const reset=e.target.closest?.('[data-offer-gallery-reset]');if(reset){e.preventDefault();saveGallerySlot(Number(reset.dataset.offerGalleryReset),DEFAULT_GALLERY[Number(reset.dataset.offerGalleryReset)]);toast('Фото возвращено к стандартному.','success');return}
const edit=e.target.closest?.('#sunOfferEdit');if(edit&&modal()?.classList.contains('on')){e.preventDefault();e.stopImmediatePropagation();setMode(mode==='editor'?'preview':'editor');return;}
const pdf=e.target.closest?.('#sunOfferPdf');if(pdf&&modal()?.classList.contains('on')&&mode!=='preview')setMode('preview',false);
},true);
window.addEventListener('sun:offer-gallery-v1769-change',syncGalleryUi);
let enhanceQueued=false;
const observer=new MutationObserver(()=>{if(enhanceQueued)return;enhanceQueued=true;queueMicrotask(()=>{enhanceQueued=false;enhance()})});
observer.observe(document.documentElement,{subtree:true,childList:true});
installStyle();enhance();
const retry=setInterval(()=>{if(patchOpeners()&&patchClassic())clearInterval(retry)},350);setTimeout(()=>clearInterval(retry),15000);
window.SunOfferWorkspaceV1769={VERSION,RELEASE,GALLERY_KEY,setMode,enhance,galleryFor,checks:()=>({version:VERSION,mode,modal:Boolean(modal()),tabs:Boolean($('sunOfferWorkspaceTabsV1769')),activeOrderId,openersPatched,classicPatched})};
})();

View File

@ -1,7 +1,7 @@
(()=>{ (()=>{
'use strict'; 'use strict';
const VERSION='17.6.8'; const VERSION='17.6.9';
const RELEASE='20260908-v17-6-8-developer-console-ux'; const RELEASE='20260908-v17-6-9-offer-pdf-tabs-gallery';
const critical=img=>img.closest('header,.brand,#sunCloudAuthGate,.sun-auth-gate')||img.id==='sunLoginLogo'||img.classList.contains('sun-live-catalog-logo'); const critical=img=>img.closest('header,.brand,#sunCloudAuthGate,.sun-auth-gate')||img.id==='sunLoginLogo'||img.classList.contains('sun-live-catalog-logo');
const tune=img=>{ const tune=img=>{
if(!(img instanceof HTMLImageElement)||critical(img))return; if(!(img instanceof HTMLImageElement)||critical(img))return;
@ -107,14 +107,18 @@
if(window.SunDeveloperUXV1768||document.getElementById('sunDeveloperUXV1768Script'))return; if(window.SunDeveloperUXV1768||document.getElementById('sunDeveloperUXV1768Script'))return;
const script=document.createElement('script');script.id='sunDeveloperUXV1768Script';script.src=`core/developer-console-v1768.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль developer-console-v1768.js');document.head.appendChild(script); const script=document.createElement('script');script.id='sunDeveloperUXV1768Script';script.src=`core/developer-console-v1768.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль developer-console-v1768.js');document.head.appendChild(script);
} }
function loadOfferWorkspace(){
if(window.SunOfferWorkspaceV1769||document.getElementById('sunOfferWorkspaceV1769Script'))return;
const script=document.createElement('script');script.id='sunOfferWorkspaceV1769Script';script.src=`core/offer-workspace-v1769.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль offer-workspace-v1769.js');document.head.appendChild(script);
}
const start=()=>{ const start=()=>{
loadHotfix();loadOpsUX();loadUXFix();loadDeveloperUX();scan(document);startMemoryTimer(); loadHotfix();loadOpsUX();loadUXFix();loadDeveloperUX();loadOfferWorkspace();scan(document);startMemoryTimer();
const mo=new MutationObserver(records=>{records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)queueImageScan(n)}));}); const mo=new MutationObserver(records=>{records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)queueImageScan(n)}));});
mo.observe(document.documentElement,{childList:true,subtree:true}); mo.observe(document.documentElement,{childList:true,subtree:true});
document.addEventListener('click',e=>{if(e.target.closest('#sunDeveloperNavV22,[data-dev-tab],#sunDevRefresh'))scheduleMemoryRefresh(100,true)},true); document.addEventListener('click',e=>{if(e.target.closest('#sunDeveloperNavV22,[data-dev-tab],#sunDevRefresh'))scheduleMemoryRefresh(100,true)},true);
window.addEventListener('sun:cloud-state-applied',()=>scheduleMemoryRefresh(180,true)); window.addEventListener('sun:cloud-state-applied',()=>scheduleMemoryRefresh(180,true));
document.addEventListener('visibilitychange',()=>{if(!document.hidden&&developerVisible())scheduleMemoryRefresh(50,false)}); document.addEventListener('visibilitychange',()=>{if(!document.hidden&&developerVisible())scheduleMemoryRefresh(50,false)});
window.SunPerformance={VERSION,scanImages:()=>scan(document),refreshDeveloperMemory:(force=true)=>enhanceDeveloperMemory({force}),loadHotfix,loadOpsUX,loadUXFix,loadDeveloperUX,disconnect:()=>{mo.disconnect();if(memoryTimer){clearInterval(memoryTimer);memoryTimer=0}if(imageScanTimer){clearTimeout(imageScanTimer);imageScanTimer=0}pendingImageRoots.clear();}}; window.SunPerformance={VERSION,scanImages:()=>scan(document),refreshDeveloperMemory:(force=true)=>enhanceDeveloperMemory({force}),loadHotfix,loadOpsUX,loadUXFix,loadDeveloperUX,loadOfferWorkspace,disconnect:()=>{mo.disconnect();if(memoryTimer){clearInterval(memoryTimer);memoryTimer=0}if(imageScanTimer){clearTimeout(imageScanTimer);imageScanTimer=0}pendingImageRoots.clear();}};
}; };
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start,{once:true});else start(); if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start,{once:true});else start();
})(); })();

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,8 @@
const CACHE='sun-catering-pwa-v73-20260908-v17-6-8-developer-console-ux'; const CACHE='sun-catering-pwa-v74-20260908-v17-6-9-offer-pdf-tabs-gallery';
const VERSION='20260908-v17-6-8-developer-console-ux'; const VERSION='20260908-v17-6-9-offer-pdf-tabs-gallery';
const CORE=[ const CORE=[
'./','./index.html', './','./index.html',
`./core/sun-safe.js?v=${VERSION}`,`./core/performance.js?v=${VERSION}`,`./core/hotfix-v1763.js?v=${VERSION}`,`./core/ops-ux-v1762.js?v=${VERSION}`,`./core/ux-fixes-v1764.js?v=${VERSION}`,`./core/pdf-engine.js?v=${VERSION}`,`./core/classic-offer-pdf-v1767.js?v=${VERSION}`,`./core/developer-console-v1768.js?v=${VERSION}`,`./legacy/bootstrap.js?v=${VERSION}`,`./app-runtime.js?v=${VERSION}`, `./core/sun-safe.js?v=${VERSION}`,`./core/performance.js?v=${VERSION}`,`./core/hotfix-v1763.js?v=${VERSION}`,`./core/ops-ux-v1762.js?v=${VERSION}`,`./core/ux-fixes-v1764.js?v=${VERSION}`,`./core/pdf-engine.js?v=${VERSION}`,`./core/classic-offer-pdf-v1767.js?v=${VERSION}`,`./core/developer-console-v1768.js?v=${VERSION}`,`./core/offer-workspace-v1769.js?v=${VERSION}`,`./legacy/bootstrap.js?v=${VERSION}`,`./app-runtime.js?v=${VERSION}`,
'./offer-gallery/001.jpg','./offer-gallery/002.jpg', './offer-gallery/001.jpg','./offer-gallery/002.jpg',
'./catalog/001.jpg','./catalog/002.jpg','./catalog/003.jpg', './catalog/001.jpg','./catalog/002.jpg','./catalog/003.jpg',
'./sun-logo.png','./caterium-login-logo.png','./pwa-icon-192.png','./pwa-icon-512.png','./manifest.webmanifest', './sun-logo.png','./caterium-login-logo.png','./pwa-icon-192.png','./pwa-icon-512.png','./manifest.webmanifest',

View File

@ -156,7 +156,7 @@ test('Developer Console memory refresh is bounded and does not react to its own
const result=await page.evaluate(()=>({count:window.__rpcCount,box:document.querySelectorAll('#sunDevMemoryV1761').length,version:document.getElementById('sunDevReleaseVersion')?.textContent})); const result=await page.evaluate(()=>({count:window.__rpcCount,box:document.querySelectorAll('#sunDevMemoryV1761').length,version:document.getElementById('sunDevReleaseVersion')?.textContent}));
expect(result.count).toBeLessThanOrEqual(2); expect(result.count).toBeLessThanOrEqual(2);
expect(result.box).toBe(1); expect(result.box).toBe(1);
expect(result.version).toBe('17.6.8'); expect(result.version).toBe('17.6.9');
}); });
test('mobile body does not overflow viewport', async ({ page }, testInfo) => { test('mobile body does not overflow viewport', async ({ page }, testInfo) => {
test.skip(testInfo.project.name!=='mobile-390'); await page.goto('/index.html', { waitUntil:'domcontentloaded' }); await page.waitForTimeout(500); test.skip(testInfo.project.name!=='mobile-390'); await page.goto('/index.html', { waitUntil:'domcontentloaded' }); await page.waitForTimeout(500);
@ -240,3 +240,20 @@ test('v17.6.8 Developer Console UX module boots', async ({ page }) => {
const result=await page.evaluate(()=>window.SunDeveloperUXV1768.checks()); const result=await page.evaluate(()=>window.SunDeveloperUXV1768.checks());
expect(result.version).toBe('17.6.8'); expect(result.version).toBe('17.6.8');
}); });
test('v17.6.9 offer workspace module boots', async ({ page }) => {
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
const source=fs.readFileSync(path.join(process.cwd(),'public','core','offer-workspace-v1769.js'),'utf8');
await page.evaluate(()=>{
document.body.innerHTML='<div id="sunClientOfferModal" class="modal on"><div class="dialog"><div class="sun-offer-modal-head"><button id="sunOfferPdf">Скачать PDF</button><button id="sunOfferEdit">Редактировать</button></div><section id="sunOfferEditor"><div class="sun-offer-editor-gallery"><div class="sun-offer-editor-gallery-images"><img src="offer-gallery/001.jpg"><img src="offer-gallery/002.jpg"></div></div></section><section id="sunOfferTemplateV1764"></section><div class="sun-offer-frame-wrap"><div id="sunClientOfferPreview"></div></div></div></div>';
window.sunOpenClientOffer=async()=>true;window.sunOpenCurrentClientOffer=async()=>true;
window.SunClassicOfferPDFV1767={renderPages:async()=>[]};window.SunEnterprise={toast:()=>null};
});
await page.addScriptTag({content:source});
await page.waitForFunction(()=>Boolean(window.SunOfferWorkspaceV1769),null,{timeout:5000});
const result=await page.evaluate(()=>window.SunOfferWorkspaceV1769.checks());
expect(result.version).toBe('17.6.9');
expect(result.tabs).toBe(true);
expect(await page.locator('[data-offer-gallery-slot]').count()).toBe(2);
});

View File

@ -4,7 +4,7 @@ const root=process.cwd(), pub=path.join(root,'public');
const read=p=>fs.readFileSync(path.join(pub,p),'utf8'); const read=p=>fs.readFileSync(path.join(pub,p),'utf8');
const readRoot=p=>fs.readFileSync(path.join(root,p),'utf8'); const readRoot=p=>fs.readFileSync(path.join(root,p),'utf8');
let bad=0;const check=(v,m)=>{console.log(`${v?'OK':'FAIL'}: ${m}`);if(!v)bad++}; let bad=0;const check=(v,m)=>{console.log(`${v?'OK':'FAIL'}: ${m}`);if(!v)bad++};
const index=read('index.html'),runtime=read('app-runtime.js'),sw=read('service-worker.js'),css=read('core/stability-v1760.css'),performance=read('core/performance.js'),ops=read('core/ops-ux-v1762.js'),hotfix=read('core/hotfix-v1763.js'),ux=read('core/ux-fixes-v1764.js'),classic=read('core/classic-offer-pdf-v1767.js'),developerUX=read('core/developer-console-v1768.js'); const index=read('index.html'),runtime=read('app-runtime.js'),sw=read('service-worker.js'),css=read('core/stability-v1760.css'),performance=read('core/performance.js'),ops=read('core/ops-ux-v1762.js'),hotfix=read('core/hotfix-v1763.js'),ux=read('core/ux-fixes-v1764.js'),classic=read('core/classic-offer-pdf-v1767.js'),developerUX=read('core/developer-console-v1768.js'),offerWorkspace=read('core/offer-workspace-v1769.js');
const pkg=JSON.parse(readRoot('package.json')); const pkg=JSON.parse(readRoot('package.json'));
const lock=JSON.parse(readRoot('package-lock.json')); const lock=JSON.parse(readRoot('package-lock.json'));
const releaseManifest=JSON.parse(readRoot('docs/release-manifest.json')); const releaseManifest=JSON.parse(readRoot('docs/release-manifest.json'));
@ -14,8 +14,8 @@ check(!index.includes('offer-gallery-data.js'),'blocking Base64 gallery absent')
check((runtime.match(/\/Type \/Catalog/g)||[]).length===0,'runtime contains no PDF binary writer'); check((runtime.match(/\/Type \/Catalog/g)||[]).length===0,'runtime contains no PDF binary writer');
check(read('core/pdf-engine.js').includes('595.28')&&read('core/pdf-engine.js').includes('841.89'),'PDF engine uses A4 MediaBox'); check(read('core/pdf-engine.js').includes('595.28')&&read('core/pdf-engine.js').includes('841.89'),'PDF engine uses A4 MediaBox');
check([...index.matchAll(/@page\{([^}]*)\}/g)].every(m=>/size:A4/i.test(m[1])),'compact @page rules use A4'); check([...index.matchAll(/@page\{([^}]*)\}/g)].every(m=>/size:A4/i.test(m[1])),'compact @page rules use A4');
check(sw.includes('v73-20260908-v17-6-8-developer-console-ux')&&sw.includes('classic-offer-pdf-v1767.js')&&sw.includes('developer-console-v1768.js'),'service worker cache is v17.6.8'); check(sw.includes('v74-20260908-v17-6-9-offer-pdf-tabs-gallery')&&sw.includes('classic-offer-pdf-v1767.js')&&sw.includes('developer-console-v1768.js')&&sw.includes('offer-workspace-v1769.js'),'service worker cache is v17.6.9');
check(index.includes('20260908-v17-6-8-developer-console-ux')&&index.includes('classic-offer-pdf-v1767.js')&&!index.includes('20260907-v17-6-0-stability-security'),'index cache-busting points to v17.6.8'); check(index.includes('20260908-v17-6-9-offer-pdf-tabs-gallery')&&index.includes('classic-offer-pdf-v1767.js')&&!index.includes('20260907-v17-6-0-stability-security'),'index cache-busting points to v17.6.9');
check(performance.includes('SunAttachmentGuard')&&performance.includes('TARGET=2*1024*1024'),'chat photo auto-compression is versioned'); check(performance.includes('SunAttachmentGuard')&&performance.includes('TARGET=2*1024*1024'),'chat photo auto-compression is versioned');
check(performance.includes("rpc('sun_dev_dashboard')")&&performance.includes('server_size')&&performance.includes('storage_size'),'Developer Console server/storage counters are versioned'); check(performance.includes("rpc('sun_dev_dashboard')")&&performance.includes('server_size')&&performance.includes('storage_size'),'Developer Console server/storage counters are versioned');
check(performance.includes('MEMORY_REFRESH_MS=30000')&&performance.includes('MEMORY_TIMEOUT_MS=8000')&&performance.includes('memoryPromise'),'Developer Console memory refresh is bounded'); check(performance.includes('MEMORY_REFRESH_MS=30000')&&performance.includes('MEMORY_TIMEOUT_MS=8000')&&performance.includes('memoryPromise'),'Developer Console memory refresh is bounded');
@ -39,8 +39,8 @@ check(!/sb_secret_[A-Za-z0-9_-]{20,}|service_role\s*[:=]\s*["'][A-Za-z0-9._-]{30
check(lock.version===pkg.version&&lock.packages?.['']?.version===pkg.version,'package.json and package-lock.json versions match'); check(lock.version===pkg.version&&lock.packages?.['']?.version===pkg.version,'package.json and package-lock.json versions match');
check(releaseManifest.version===`v${pkg.version}`,'release manifest version matches package.json'); check(releaseManifest.version===`v${pkg.version}`,'release manifest version matches package.json');
check(releaseManifest.channel==='production','release manifest channel is production'); check(releaseManifest.channel==='production','release manifest channel is production');
check(String(releaseManifest.pwaCache||'').includes('v73-20260908-v17-6-8-developer-console-ux'),'release manifest points to current PWA cache'); check(String(releaseManifest.pwaCache||'').includes('v74-20260908-v17-6-9-offer-pdf-tabs-gallery'),'release manifest points to current PWA cache');
check(['17.6.2','17.6.3','17.6.4','17.6.5','17.6.6','17.6.7','17.6.8'].every(v=>fs.existsSync(path.join(root,`docs/releases/V${v}-CHANGES.txt`))),'release notes exist through v17.6.8'); check(['17.6.2','17.6.3','17.6.4','17.6.5','17.6.6','17.6.7','17.6.8','17.6.9'].every(v=>fs.existsSync(path.join(root,`docs/releases/V${v}-CHANGES.txt`))),'release notes exist through v17.6.9');
check(runtime.includes('CLOUD_RPC_TIMEOUT_MS=12000')&&runtime.includes('CLOUD_CONFLICT_MAX_RETRIES=4')&&runtime.includes('retryCount'),'cloud sync has timeout and capped exponential conflict retries'); check(runtime.includes('CLOUD_RPC_TIMEOUT_MS=12000')&&runtime.includes('CLOUD_CONFLICT_MAX_RETRIES=4')&&runtime.includes('retryCount'),'cloud sync has timeout and capped exponential conflict retries');
check(runtime.includes("const VERSION = '17.6.5'")&&runtime.includes('ERROR_DEDUPE_MS=5*60*1000')&&runtime.includes('mirrorBusy=false')&&runtime.includes('backupBusy=false'),'stability logger uses current version, dedupe and single-flight guards'); check(runtime.includes("const VERSION = '17.6.5'")&&runtime.includes('ERROR_DEDUPE_MS=5*60*1000')&&runtime.includes('mirrorBusy=false')&&runtime.includes('backupBusy=false'),'stability logger uses current version, dedupe and single-flight guards');
check(runtime.includes('refreshSupportWorkspace')&&runtime.includes('sun_dev_support_snapshot'),'cloud exposes lightweight read-only support refresh'); check(runtime.includes('refreshSupportWorkspace')&&runtime.includes('sun_dev_support_snapshot'),'cloud exposes lightweight read-only support refresh');
@ -49,7 +49,7 @@ check(performance.includes('pendingImageRoots')&&performance.includes('queueImag
check(runtime.includes('explicitTemplate')&&runtime.includes("OFFER_TEMPLATE_IDS.has(explicitTemplate)"),'per-order proposal template survives render and PDF'); check(runtime.includes('explicitTemplate')&&runtime.includes("OFFER_TEMPLATE_IDS.has(explicitTemplate)"),'per-order proposal template survives render and PDF');
check(runtime.includes("else if(id==='midnight-glass')")&&runtime.includes("else if(id==='emerald-gold')")&&runtime.includes("if(id==='editorial-grid')"),'existing proposal layout sequences remain available'); check(runtime.includes("else if(id==='midnight-glass')")&&runtime.includes("else if(id==='emerald-gold')")&&runtime.includes("if(id==='editorial-grid')"),'existing proposal layout sequences remain available');
check(runtime.includes('data-template-mini')&&ux.includes('data-template-mini'),'global and per-offer selectors show structural PDF previews'); check(runtime.includes('data-template-mini')&&ux.includes('data-template-mini'),'global and per-offer selectors show structural PDF previews');
check(pkg.version==='17.6.8','package version is v17.6.8'); check(pkg.version==='17.6.9','package version is v17.6.9');
check(classic.includes("const VERSION='17.6.7'")&&classic.includes('CLASSIC_IDS')&&classic.includes('ARCHIVE_IDS'),'classic proposal PDF module is v17.6.7'); check(classic.includes("const VERSION='17.6.7'")&&classic.includes('CLASSIC_IDS')&&classic.includes('ARCHIVE_IDS'),'classic proposal PDF module is v17.6.7');
check(runtime.includes("'warm-sun'")&&runtime.includes("'bento-cards'")&&runtime.includes("'event-story'")&&runtime.includes("'personal-letter'")&&runtime.includes("'event-ticket'")&&runtime.includes("'solar-experience'"),'10 classic proposal designs are available'); check(runtime.includes("'warm-sun'")&&runtime.includes("'bento-cards'")&&runtime.includes("'event-story'")&&runtime.includes("'personal-letter'")&&runtime.includes("'event-ticket'")&&runtime.includes("'solar-experience'"),'10 classic proposal designs are available');
check(runtime.includes("'midnight-compact'")&&runtime.includes("'black-gold'")&&runtime.includes("'neon-emerald'"),'hidden archive proposal templates are restored'); check(runtime.includes("'midnight-compact'")&&runtime.includes("'black-gold'")&&runtime.includes("'neon-emerald'"),'hidden archive proposal templates are restored');
@ -60,4 +60,8 @@ check(developerUX.includes("const VERSION='17.6.8'")&&developerUX.includes('sun_
check(developerUX.includes('selectedAccountsWorkspace')&&developerUX.includes('sun-dev-plan-matrix'),'accounts are grouped by company and plans use compact matrix'); check(developerUX.includes('selectedAccountsWorkspace')&&developerUX.includes('sun-dev-plan-matrix'),'accounts are grouped by company and plans use compact matrix');
check(developerUX.includes('KNOWN_DOM_RACE')&&developerUX.includes('prepareAsyncRoots'),'Developer Console async DOM race is guarded'); check(developerUX.includes('KNOWN_DOM_RACE')&&developerUX.includes('prepareAsyncRoots'),'Developer Console async DOM race is guarded');
check(releaseManifest.developerCompanyNumbers===true&&releaseManifest.developerCompanyDelete===true&&releaseManifest.developerAccountsGroupedByCompany===true&&releaseManifest.developerPlansCompactMatrix===true&&releaseManifest.developerJournalGroupedErrors===true,'release manifest records Developer Console UX changes'); check(releaseManifest.developerCompanyNumbers===true&&releaseManifest.developerCompanyDelete===true&&releaseManifest.developerAccountsGroupedByCompany===true&&releaseManifest.developerPlansCompactMatrix===true&&releaseManifest.developerJournalGroupedErrors===true,'release manifest records Developer Console UX changes');
check(offerWorkspace.includes("const VERSION='17.6.9'")&&offerWorkspace.includes('sun-offer-workspace-tabs-v1769'),'offer workspace v17.6.9 is versioned');
check(offerWorkspace.includes('PDF и предпросмотр')&&offerWorkspace.includes('Оформление PDF')&&offerWorkspace.includes('data-offer-gallery-slot'),'offer workspace separates preview/editor/templates and exposes two gallery uploads');
check(offerWorkspace.includes('SunClassicOfferPDFV1767')&&offerWorkspace.includes('finalGallery=galleryFor'),'custom gallery is injected into PDF renderer');
check(releaseManifest.offerWorkspaceTabs===true&&releaseManifest.offerTemplatesSeparateTab===true&&releaseManifest.offerTwoCustomGalleryPhotos===true,'release manifest records offer workspace changes');
if(bad)process.exit(1); if(bad)process.exit(1);

View File

@ -6,7 +6,7 @@ const readPub=p=>fs.readFileSync(path.join(pub,p),'utf8');
const readRoot=p=>fs.readFileSync(path.join(root,p),'utf8'); const readRoot=p=>fs.readFileSync(path.join(root,p),'utf8');
const fail=m=>{console.error('FAIL:',m);process.exitCode=1}; const fail=m=>{console.error('FAIL:',m);process.exitCode=1};
const ok=m=>console.log('OK:',m); const ok=m=>console.log('OK:',m);
const html=readPub('index.html'),legacy=readPub('legacy/bootstrap.js'),runtime=readPub('app-runtime.js'),safe=readPub('core/sun-safe.js'),sw=readPub('service-worker.js'),performance=readPub('core/performance.js'),ops=readPub('core/ops-ux-v1762.js'),hotfix=readPub('core/hotfix-v1763.js'),ux=readPub('core/ux-fixes-v1764.js'),classic=readPub('core/classic-offer-pdf-v1767.js'),developerUX=readPub('core/developer-console-v1768.js'); const html=readPub('index.html'),legacy=readPub('legacy/bootstrap.js'),runtime=readPub('app-runtime.js'),safe=readPub('core/sun-safe.js'),sw=readPub('service-worker.js'),performance=readPub('core/performance.js'),ops=readPub('core/ops-ux-v1762.js'),hotfix=readPub('core/hotfix-v1763.js'),ux=readPub('core/ux-fixes-v1764.js'),classic=readPub('core/classic-offer-pdf-v1767.js'),developerUX=readPub('core/developer-console-v1768.js'),offerWorkspace=readPub('core/offer-workspace-v1769.js');
if(!html.includes('core/sun-safe.js'))fail('SunSafe must load before legacy modules');else ok('shared SunSafe loaded'); if(!html.includes('core/sun-safe.js'))fail('SunSafe must load before legacy modules');else ok('shared SunSafe loaded');
if(html.includes('offer-gallery-data.js')||fs.existsSync(path.join(pub,'offer-gallery-data.js')))fail('blocking offer-gallery-data.js still present');else ok('base64 gallery removed'); if(html.includes('offer-gallery-data.js')||fs.existsSync(path.join(pub,'offer-gallery-data.js')))fail('blocking offer-gallery-data.js still present');else ok('base64 gallery removed');
for(const raw of ['<span>${b.name}</span>','<span>${o.event}</span>','<span>${o.address||','value="${x[0]}"','value="${x[2]}"']) if(legacy.includes(raw)) fail(`legacy bootstrap contains raw HTML interpolation ${raw}`); for(const raw of ['<span>${b.name}</span>','<span>${o.event}</span>','<span>${o.address||','value="${x[0]}"','value="${x[2]}"']) if(legacy.includes(raw)) fail(`legacy bootstrap contains raw HTML interpolation ${raw}`);
@ -27,8 +27,8 @@ if(current!==113)fail(`current catalog photo count ${current}, expected 113`);el
if(legacyCount!==60)fail(`legacy catalog photo count ${legacyCount}, expected 60`);else ok('60 legacy catalog photos'); if(legacyCount!==60)fail(`legacy catalog photo count ${legacyCount}, expected 60`);else ok('60 legacy catalog photos');
const gallery=fs.readdirSync(path.join(pub,'offer-gallery')).filter(x=>/\.jpg$/i.test(x)); const gallery=fs.readdirSync(path.join(pub,'offer-gallery')).filter(x=>/\.jpg$/i.test(x));
if(gallery.length!==2)fail(`offer gallery contains ${gallery.length} jpg files, expected 2`);else ok('offer gallery trimmed'); if(gallery.length!==2)fail(`offer gallery contains ${gallery.length} jpg files, expected 2`);else ok('offer gallery trimmed');
if(!sw.includes('v17-6-8-developer-console-ux')||!sw.includes('developer-console-v1768.js')||!sw.includes('classic-offer-pdf-v1767.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated to v17.6.8'); if(!sw.includes('v17-6-9-offer-pdf-tabs-gallery')||!sw.includes('offer-workspace-v1769.js')||!sw.includes('developer-console-v1768.js')||!sw.includes('classic-offer-pdf-v1767.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated to v17.6.9');
if(html.includes('20260907-v17-6-0-stability-security')||!html.includes('20260908-v17-6-8-developer-console-ux')||!html.includes('classic-offer-pdf-v1767.js'))fail('index still serves stale core asset version');else ok('index cache-busting is current'); if(html.includes('20260907-v17-6-0-stability-security')||!html.includes('20260908-v17-6-9-offer-pdf-tabs-gallery')||!html.includes('classic-offer-pdf-v1767.js'))fail('index still serves stale core asset version');else ok('index cache-busting is current');
if(!performance.includes('SunAttachmentGuard')||!performance.includes('MAX_SIDE=2048'))fail('chat photo compression guard missing');else ok('chat photo compression guard present'); if(!performance.includes('SunAttachmentGuard')||!performance.includes('MAX_SIDE=2048'))fail('chat photo compression guard missing');else ok('chat photo compression guard present');
if(!performance.includes("rpc('sun_dev_dashboard')")||!performance.includes('storage_size')||!performance.includes('server_size'))fail('Developer Console memory counters missing');else ok('Developer Console memory counters present'); if(!performance.includes("rpc('sun_dev_dashboard')")||!performance.includes('storage_size')||!performance.includes('server_size'))fail('Developer Console memory counters missing');else ok('Developer Console memory counters present');
if(performance.includes('records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));enhanceDeveloperMemory()'))fail('Developer Console memory refresh is still coupled to MutationObserver');else ok('Developer Console memory refresh loop removed'); if(performance.includes('records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));enhanceDeveloperMemory()'))fail('Developer Console memory refresh is still coupled to MutationObserver');else ok('Developer Console memory refresh loop removed');
@ -45,5 +45,6 @@ if(!hotfix.includes('checkPlatformAdmin?.(false)')||!hotfix.includes('},10000);'
if(!ux.includes('sunMenuIconV1766')||!ux.includes('sun-offer-template-mini-editorial-grid')||!runtime.includes('explicitTemplate'))fail('v17.6.6 proposal/menu markers missing');else ok('v17.6.6 proposal/menu markers present'); if(!ux.includes('sunMenuIconV1766')||!ux.includes('sun-offer-template-mini-editorial-grid')||!runtime.includes('explicitTemplate'))fail('v17.6.6 proposal/menu markers missing');else ok('v17.6.6 proposal/menu markers present');
if(!classic.includes("const VERSION='17.6.7'")||!classic.includes('CLASSIC_IDS')||!classic.includes('ARCHIVE_IDS')||!classic.includes('renderPages'))fail('v17.6.7 classic PDF module missing');else ok('v17.6.7 classic PDF module present'); if(!classic.includes("const VERSION='17.6.7'")||!classic.includes('CLASSIC_IDS')||!classic.includes('ARCHIVE_IDS')||!classic.includes('renderPages'))fail('v17.6.7 classic PDF module missing');else ok('v17.6.7 classic PDF module present');
if(!developerUX.includes("const VERSION='17.6.8'")||!developerUX.includes('sun_dev_delete_company_v1768')||!developerUX.includes('sun_dev_error_groups_v1768')||!developerUX.includes('selectedAccountsWorkspace')||!developerUX.includes('sun-dev-plan-matrix')||!developerUX.includes('KNOWN_DOM_RACE'))fail('v17.6.8 Developer Console UX module missing');else ok('v17.6.8 Developer Console UX module present'); if(!developerUX.includes("const VERSION='17.6.8'")||!developerUX.includes('sun_dev_delete_company_v1768')||!developerUX.includes('sun_dev_error_groups_v1768')||!developerUX.includes('selectedAccountsWorkspace')||!developerUX.includes('sun-dev-plan-matrix')||!developerUX.includes('KNOWN_DOM_RACE'))fail('v17.6.8 Developer Console UX module missing');else ok('v17.6.8 Developer Console UX module present');
if(/service_role\s*[:=]\s*['"][A-Za-z0-9._-]{20,}/i.test(ops+hotfix+ux+classic+developerUX)||/eyJ[a-zA-Z0-9_-]{30,}/.test(ops+hotfix+ux+classic+developerUX))fail('possible secret in ops/hotfix/ux/classic/developer module');else ok('ops/hotfix/ux/classic/developer modules have no hard-coded secret'); if(!offerWorkspace.includes("const VERSION='17.6.9'")||!offerWorkspace.includes('data-offer-workspace-tab=\"templates\"')||!offerWorkspace.includes('data-offer-gallery-slot')||!offerWorkspace.includes('sunOfferGalleryOverridesV1769'))fail('v17.6.9 offer workspace module missing');else ok('v17.6.9 offer workspace module present');
if(/service_role\s*[:=]\s*['"][A-Za-z0-9._-]{20,}/i.test(ops+hotfix+ux+classic+developerUX+offerWorkspace)||/eyJ[a-zA-Z0-9_-]{30,}/.test(ops+hotfix+ux+classic+developerUX+offerWorkspace))fail('possible secret in ops/hotfix/ux/classic/developer/offer module');else ok('ops/hotfix/ux/classic/developer/offer modules have no hard-coded secret');
if(process.exitCode)process.exit(process.exitCode); if(process.exitCode)process.exit(process.exitCode);