Caterium v17.6.4 — settings, orders, PDFs and menu icon

Fix Developer Console memory refresh freeze, auto-complete and fully pay orders one minute after scheduled time, persist offer template per client proposal, and add styled Menu SVG icon. Includes PWA cache update and regression coverage.
This commit is contained in:
pavlov346346-source 2026-09-07 20:05:43 +03:00 committed by GitHub
parent ce6fa974aa
commit 8c90dd950f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 355 additions and 31 deletions

View File

@ -1,10 +1,10 @@
{ {
"name": "caterium-app", "name": "caterium-app",
"private": true, "private": true,
"version": "17.6.3", "version": "17.6.4",
"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/pdf-engine.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",
"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

@ -1,5 +1,7 @@
(()=>{ (()=>{
'use strict'; 'use strict';
const VERSION='17.6.4';
const RELEASE='20260907-v17-6-4-settings-orders-pdf-menu';
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;
@ -53,39 +55,60 @@
const input=e.target;if(!(input instanceof HTMLInputElement)||input.id!=='sunChatFilesV29'||guardedInputs.has(input)||typeof DataTransfer==='undefined')return; const input=e.target;if(!(input instanceof HTMLInputElement)||input.id!=='sunChatFilesV29'||guardedInputs.has(input)||typeof DataTransfer==='undefined')return;
e.preventDefault();e.stopImmediatePropagation();guardChatFiles(input).catch(err=>{console.error('[Caterium photo compression]',err);guardedInputs.add(input);input.dispatchEvent(new Event('change',{bubbles:true}));guardedInputs.delete(input);}); e.preventDefault();e.stopImmediatePropagation();guardChatFiles(input).catch(err=>{console.error('[Caterium photo compression]',err);guardedInputs.add(input);input.dispatchEvent(new Event('change',{bubbles:true}));guardedInputs.delete(input);});
},true); },true);
window.SunAttachmentGuard={VERSION:'17.6.3',MAX_FILE,TARGET,MAX_SIDE,compressImage,prepareAttachment}; window.SunAttachmentGuard={VERSION,MAX_FILE,TARGET,MAX_SIDE,compressImage,prepareAttachment};
let memoryData=null,memoryAt=0,memoryLoading=false; let memoryData=null,memoryAt=0,memoryPromise=null,memoryTimer=0;
const MEMORY_CACHE_MS=15000,MEMORY_REFRESH_MS=30000,MEMORY_TIMEOUT_MS=8000;
const esc=v=>window.SunSafe?.escapeHTML?window.SunSafe.escapeHTML(String(v??'')):String(v??''); const esc=v=>window.SunSafe?.escapeHTML?window.SunSafe.escapeHTML(String(v??'')):String(v??'');
async function loadMemory(){ const developerVisible=()=>document.getElementById('sun-developer-console-v22')?.classList.contains('on');
const now=Date.now();if(memoryLoading)return memoryData;if(memoryData&&now-memoryAt<5000)return memoryData; async function loadMemory({force=false}={}){
const c=window.SunCloudV2?.getClient?.();if(!c)return null;memoryLoading=true; const now=Date.now();if(memoryPromise)return memoryPromise;if(!force&&memoryData&&now-memoryAt<MEMORY_CACHE_MS)return memoryData;
try{const r=await c.rpc('sun_dev_dashboard');if(r.error)throw r.error;memoryData=r.data||null;memoryAt=Date.now();return memoryData;}catch(_){return null}finally{memoryLoading=false;} const c=window.SunCloudV2?.getClient?.();if(!c)return null;
memoryPromise=(async()=>{
let timer=0;
try{
const timeout=new Promise((_,reject)=>{timer=setTimeout(()=>reject(new Error('Developer memory timeout')),MEMORY_TIMEOUT_MS)});
const rpc=Promise.resolve(c.rpc('sun_dev_dashboard'));
const r=await Promise.race([rpc,timeout]);
if(r?.error)throw r.error;memoryData=r?.data||null;memoryAt=Date.now();return memoryData;
}catch(error){console.warn('[Caterium developer memory]',error?.message||error);return memoryData}
finally{clearTimeout(timer);memoryPromise=null}
})();
return memoryPromise;
} }
async function enhanceDeveloperMemory(){ async function enhanceDeveloperMemory({force=false}={}){
const view=document.getElementById('sun-developer-console-v22');if(!view?.classList.contains('on'))return; const view=document.getElementById('sun-developer-console-v22');if(!view?.classList.contains('on'))return null;
const body=document.getElementById('sunDevBody');if(!body)return; const body=document.getElementById('sunDevBody');if(!body)return null;
const version=document.getElementById('sunDevReleaseVersion');if(version)version.textContent='17.6.3'; const version=document.getElementById('sunDevReleaseVersion');if(version)version.textContent=VERSION;
const d=await loadMemory();if(!d||!view.classList.contains('on'))return; const d=await loadMemory({force});if(!d||!developerVisible())return d;
let box=document.getElementById('sunDevMemoryV1761');if(!box){box=document.createElement('div');box.id='sunDevMemoryV1761';box.className='sun-dev-grid';box.style.marginBottom='12px';body.prepend(box);} let box=document.getElementById('sunDevMemoryV1761');if(!box){box=document.createElement('div');box.id='sunDevMemoryV1761';box.className='sun-dev-grid';box.style.marginBottom='12px';body.prepend(box);}
box.innerHTML=`<div class="sun-dev-kpi"><small>Память сервера</small><b>${esc(d.server_size||d.database_size||'—')}</b></div><div class="sun-dev-kpi"><small>База PostgreSQL</small><b>${esc(d.database_size||'—')}</b></div><div class="sun-dev-kpi"><small>Файлы Storage</small><b>${esc(d.storage_size||'—')}</b></div><div class="sun-dev-kpi"><small>Объектов Storage</small><b>${Number(d.storage_objects||0)}</b></div>`; const html=`<div class="sun-dev-kpi"><small>Память сервера</small><b>${esc(d.server_size||d.database_size||'—')}</b></div><div class="sun-dev-kpi"><small>База PostgreSQL</small><b>${esc(d.database_size||'—')}</b></div><div class="sun-dev-kpi"><small>Файлы Storage</small><b>${esc(d.storage_size||'—')}</b></div><div class="sun-dev-kpi"><small>Объектов Storage</small><b>${Number(d.storage_objects||0)}</b></div>`;
if(box.innerHTML!==html)box.innerHTML=html;
return d;
} }
function scheduleMemoryRefresh(delay=80,force=false){setTimeout(()=>{if(developerVisible())enhanceDeveloperMemory({force}).catch(()=>{})},delay)}
function startMemoryTimer(){if(memoryTimer)return;memoryTimer=setInterval(()=>{if(!document.hidden&&developerVisible())enhanceDeveloperMemory({force:true}).catch(()=>{})},MEMORY_REFRESH_MS)}
function loadHotfix(){ function loadHotfix(){
if(window.SunHotfixV1763||document.getElementById('sunHotfixV1763Script'))return; if(window.SunHotfixV1763||document.getElementById('sunHotfixV1763Script'))return;
const script=document.createElement('script');script.id='sunHotfixV1763Script';script.src='core/hotfix-v1763.js?v=20260907-v17-6-3-developer-hotfix';script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль hotfix-v1763.js');document.head.appendChild(script); const script=document.createElement('script');script.id='sunHotfixV1763Script';script.src=`core/hotfix-v1763.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль hotfix-v1763.js');document.head.appendChild(script);
} }
function loadOpsUX(){ function loadOpsUX(){
if(window.SunOpsUXV1762||document.getElementById('sunOpsUXV1762Script'))return; if(window.SunOpsUXV1762||document.getElementById('sunOpsUXV1762Script'))return;
const script=document.createElement('script');script.id='sunOpsUXV1762Script';script.src='core/ops-ux-v1762.js?v=20260907-v17-6-3-developer-hotfix';script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль ops-ux-v1762.js');document.head.appendChild(script); const script=document.createElement('script');script.id='sunOpsUXV1762Script';script.src=`core/ops-ux-v1762.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль ops-ux-v1762.js');document.head.appendChild(script);
}
function loadUXFix(){
if(window.SunUXFixV1764||document.getElementById('sunUXFixV1764Script'))return;
const script=document.createElement('script');script.id='sunUXFixV1764Script';script.src=`core/ux-fixes-v1764.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль ux-fixes-v1764.js');document.head.appendChild(script);
} }
const start=()=>{ const start=()=>{
loadHotfix();loadOpsUX();scan(document); loadHotfix();loadOpsUX();loadUXFix();scan(document);startMemoryTimer();
const mo=new MutationObserver(records=>{records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));enhanceDeveloperMemory();}); const mo=new MutationObserver(records=>{records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(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'))setTimeout(enhanceDeveloperMemory,100)},true); document.addEventListener('click',e=>{if(e.target.closest('#sunDeveloperNavV22,[data-dev-tab],#sunDevRefresh'))scheduleMemoryRefresh(100,true)},true);
window.addEventListener('sun:cloud-state-applied',()=>setTimeout(enhanceDeveloperMemory,150)); window.addEventListener('sun:cloud-state-applied',()=>scheduleMemoryRefresh(180,true));
setInterval(()=>{if(document.getElementById('sun-developer-console-v22')?.classList.contains('on'))enhanceDeveloperMemory();},5000); document.addEventListener('visibilitychange',()=>{if(!document.hidden&&developerVisible())scheduleMemoryRefresh(50,false)});
window.SunPerformance={scanImages:()=>scan(document),refreshDeveloperMemory:enhanceDeveloperMemory,loadHotfix,loadOpsUX,disconnect:()=>mo.disconnect()}; window.SunPerformance={VERSION,scanImages:()=>scan(document),refreshDeveloperMemory:(force=true)=>enhanceDeveloperMemory({force}),loadHotfix,loadOpsUX,loadUXFix,disconnect:()=>{mo.disconnect();if(memoryTimer){clearInterval(memoryTimer);memoryTimer=0;}}};
}; };
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start,{once:true});else start(); if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start,{once:true});else start();
})(); })();

View File

@ -0,0 +1,224 @@
(()=>{
'use strict';
if(window.SunUXFixV1764)return;
const VERSION='17.6.4';
const RELEASE_DAY='2026-09-07';
const AUTO_DELAY_MS=60*1000;
const AUTO_POLL_MS=15000;
const TEMPLATE_IDS=new Set(['light','editorial-grid','midnight-glass','emerald-gold']);
const $=id=>document.getElementById(id);
const qa=(s,r=document)=>[...r.querySelectorAll(s)];
const esc=v=>window.SunSafe?.escapeHTML?window.SunSafe.escapeHTML(String(v??'')):String(v??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const toast=(text,type='info',ms=4200)=>{try{return window.SunEnterprise?.toast?.(text,type,ms)}catch(_){};};
const clone=value=>{try{return structuredClone(value)}catch(_){return JSON.parse(JSON.stringify(value))}};
function runtimeOrders(){
try{if(typeof orders!=='undefined'&&Array.isArray(orders))return orders}catch(_){}
return null;
}
function readOrders(){
const live=runtimeOrders();if(live)return live;
try{const value=JSON.parse(localStorage.getItem('sunOrders')||'[]');return Array.isArray(value)?value:[]}catch(_){return[]}
}
function persistOrders(list){
try{
const live=runtimeOrders();
if(live&&live!==list){live.length=0;live.push(...list)}
if(typeof persist==='function')persist();
else localStorage.setItem('sunOrders',JSON.stringify(list));
return true;
}catch(error){
try{localStorage.setItem('sunOrders',JSON.stringify(list));return true}catch(_){console.error('[Caterium v17.6.4] order persist failed',error);return false}
}
}
function orderById(id){return readOrders().find(o=>String(o?.id)===String(id))||null}
function currentDraftId(){try{return draft?.id??null}catch(_){return null}}
function isSupportReadOnly(){
try{if(window.SunCloudV2?.getSupportMode?.()||window.SunCloudV2?.isSupportMode?.())return true}catch(_){}
return false;
}
function canAutoWrite(){
if(isSupportReadOnly())return false;
const cloud=window.SunCloudV2;
try{
const signed=Boolean(cloud?.getSession?.()?.user);
if(!signed)return true;
if(typeof cloud?.hasPermission==='function')return cloud.hasPermission('orders.edit')||cloud.hasPermission('app.write');
}catch(_){}
return true;
}
function orderTotal(order){
try{const value=Number(window.orderTotalValue?.(order));if(Number.isFinite(value))return Math.max(0,value)}catch(_){}
const direct=Number(order?.total);return Number.isFinite(direct)?Math.max(0,direct):0;
}
function dueAt(order){
const date=String(order?.date||''),time=String(order?.time||'');
if(!/^\d{4}-\d{2}-\d{2}$/.test(date)||!/^\d{2}:\d{2}$/.test(time)||date<RELEASE_DAY)return NaN;
const stamp=new Date(`${date}T${time}:00`).getTime();return Number.isFinite(stamp)?stamp+AUTO_DELAY_MS:NaN;
}
function isAutoCompleted(order){return Boolean(order?.sunAutoCompletedAt||order?.sunAutoCompletedV1764)}
function markOrderCompleted(order,nowMs){
const total=orderTotal(order),iso=new Date(nowMs).toISOString();
order.prepayment=total;
order.balance=0;
order.status='Отдан заказчику';
order.paymentStatus='paid';
order.completedAt=order.completedAt||iso;
order.paymentCompletedAt=order.paymentCompletedAt||iso;
order.sunAutoCompletedAt=iso;
order.sunAutoCompletedV1764=true;
order.sunAutoCompletedVersion=VERSION;
return total;
}
function autoCompleteOrders(nowMs=Date.now(),{silent=false}={}){
if(!canAutoWrite())return {changed:0,ids:[]};
const list=readOrders(),ids=[];
for(const order of list){
if(!order||String(order.status||'').trim()==='Отменён'||isAutoCompleted(order))continue;
const due=dueAt(order);if(!Number.isFinite(due)||nowMs<due)continue;
markOrderCompleted(order,nowMs);ids.push(String(order.id));
}
if(!ids.length)return {changed:0,ids};
if(!persistOrders(list))return {changed:0,ids:[]};
try{window.render?.();window.renderOrders?.();window.renderStats?.();window.renderClients?.()}catch(_){}
try{window.dispatchEvent(new CustomEvent('sun:orders-auto-completed',{detail:{ids,version:VERSION}}))}catch(_){}
if(!silent){const label=ids.length===1?`Заказ №${ids[0]} автоматически завершён и полностью оплачен.`:`${ids.length} заказа автоматически завершены и полностью оплачены.`;toast(label,'success',5000)}
return {changed:ids.length,ids};
}
function localToday(){const d=new Date();return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`}
function classificationDate(order,today=localToday()){
const date=String(order?.date||'');
if(isAutoCompleted(order)&&/^\d{4}-\d{2}-\d{2}$/.test(date)&&date>=today)return '0001-01-01';
return date;
}
function cardDateParts(raw){
const d=raw?new Date(`${raw}T12:00:00`):null;
if(!d||Number.isNaN(d.getTime()))return {day:'—',month:'',weekday:'Дата'};
return {day:String(d.getDate()).padStart(2,'0'),month:d.toLocaleDateString('ru-RU',{month:'short'}).replace('.',''),weekday:d.toLocaleDateString('ru-RU',{weekday:'short'}).replace('.','')};
}
function restoreRenderedOrderDates(){
const map=new Map(readOrders().map(o=>[String(o?.id),o]));
qa('#sunOrderCards .sun-order-card').forEach(card=>{
const order=map.get(String(card.dataset.orderId||''));if(!order||!isAutoCompleted(order))return;
const tile=card.querySelector('.sun-order-card-date'),parts=cardDateParts(order.date);if(!tile)return;
const day=tile.querySelector('strong'),month=tile.querySelector('span'),small=tile.querySelector('small');
if(day)day.textContent=parts.day;if(month)month.textContent=parts.month;if(small)small.textContent=order.time||parts.weekday;
card.dataset.sunAutoCompleted='1';
});
qa('#ordersList .row').forEach(row=>{
const id=String(row.querySelector('span')?.textContent||'').replace(/\D/g,'');const order=map.get(id);if(!order||!isAutoCompleted(order))return;
if(row.children[2])row.children[2].textContent=[order.date,order.time].filter(Boolean).join(' · ');
row.dataset.sunAutoCompleted='1';
});
}
function patchOrdersRender(){
const current=window.renderOrders;if(typeof current!=='function'||current.__sunV1764)return false;
const wrapped=function(...args){
const live=runtimeOrders(),changed=[];const today=localToday();
if(live){for(const order of live){const fake=classificationDate(order,today);if(fake&&fake!==order.date){changed.push([order,order.date]);order.date=fake}}}
let result;
try{result=current.apply(this,args)}finally{for(const [order,date] of changed)order.date=date}
restoreRenderedOrderDates();setTimeout(restoreRenderedOrderDates,0);return result;
};
wrapped.__sunV1764=true;wrapped.__sunV1764Base=current;window.renderOrders=wrapped;return true;
}
let activeOfferOrderId=null,offerApiPatched=false,originalOfferGet=null;
function templateList(){try{return window.SunOfferTemplate?.list?.()||[]}catch(_){return[]}}
function validTemplate(id){return TEMPLATE_IDS.has(String(id||''))}
function storedTemplateId(order){
const id=order?.clientOfferSnapshot?.offerTemplateId||order?.clientOfferTemplateId||'';return validTemplate(id)?String(id):'';
}
function globalTemplateId(){
try{const state=originalOfferGet?originalOfferGet():window.SunOfferTemplate?.get?.();return validTemplate(state?.id)?state.id:'light'}catch(_){return'light'}
}
function activeTemplateId(){return storedTemplateId(orderById(activeOfferOrderId))||globalTemplateId()}
function patchOfferTemplateApi(){
const api=window.SunOfferTemplate;if(!api||offerApiPatched||typeof api.get!=='function')return false;
originalOfferGet=api.get.bind(api);
api.get=()=>{
const base=originalOfferGet();
const modal=$('sunClientOfferModal');if(!activeOfferOrderId||!modal?.classList.contains('on'))return base;
const id=activeTemplateId(),template=templateList().find(x=>x.id===id)||base?.template;
return {...base,id,template:template?{...template}:template};
};
offerApiPatched=true;return true;
}
function persistOfferTemplate(orderId,id){
if(!orderId||!validTemplate(id))return false;
const list=readOrders(),order=list.find(o=>String(o?.id)===String(orderId));if(!order)return false;
order.clientOfferTemplateId=id;
if(order.clientOfferSnapshot&&typeof order.clientOfferSnapshot==='object')order.clientOfferSnapshot.offerTemplateId=id;
try{if(typeof draft!=='undefined'&&draft&&String(draft.id)===String(orderId)){draft.clientOfferTemplateId=id;if(draft.clientOfferSnapshot)draft.clientOfferSnapshot.offerTemplateId=id}}catch(_){}
const ok=persistOrders(list);if(ok)try{window.dispatchEvent(new CustomEvent('sunoffertemplatechange',{detail:{id,orderId:String(orderId),perOffer:true}}))}catch(_){}
return ok;
}
function ensureOfferTemplateStored(){
const order=orderById(activeOfferOrderId);if(!order||storedTemplateId(order))return storedTemplateId(order);
const id=globalTemplateId();persistOfferTemplate(activeOfferOrderId,id);return id;
}
function pickerHtml(){
const current=activeTemplateId();return `<div class="sun-v1764-offer-template-head"><div><b>Оформление PDF</b><small>Шаблон закрепляется только за этим предложением.</small></div></div><div class="sun-v1764-offer-template-grid">${templateList().map(t=>`<button type="button" data-v1764-offer-template="${esc(t.id)}" class="${t.id===current?'on':''}"><img src="${esc(t.thumb||'')}" alt=""><span>${esc(t.name||t.id)}</span></button>`).join('')}</div>`;
}
function ensureOfferPicker(){
patchOfferTemplateApi();const modal=$('sunClientOfferModal'),dialog=modal?.querySelector('.dialog');if(!dialog||!activeOfferOrderId)return false;
ensureOfferTemplateStored();let box=$('sunOfferTemplateV1764');
if(!box){box=document.createElement('section');box.id='sunOfferTemplateV1764';box.className='sun-v1764-offer-template';const head=dialog.querySelector('.sun-offer-modal-head');if(head)head.insertAdjacentElement('afterend',box);else dialog.prepend(box);box.addEventListener('click',e=>{const b=e.target.closest('[data-v1764-offer-template]');if(!b)return;const id=b.dataset.v1764OfferTemplate;if(!persistOfferTemplate(activeOfferOrderId,id))return;renderOfferPicker();toast(`Оформление «${templateList().find(x=>x.id===id)?.name||id}» сохранено для этого предложения.`,'success',3200)});}
renderOfferPicker();return true;
}
function renderOfferPicker(){const box=$('sunOfferTemplateV1764');if(box)box.innerHTML=pickerHtml()}
function setActiveOffer(id){if(id==null||id==='')return;activeOfferOrderId=String(id);setTimeout(ensureOfferPicker,0)}
function clearActiveOffer(){activeOfferOrderId=null}
function patchOfferOpenExports(){
const one=window.sunOpenClientOffer;if(typeof one==='function'&&!one.__sunV1764){const w=function(id,...rest){setActiveOffer(id);return one.call(this,id,...rest)};w.__sunV1764=true;window.sunOpenClientOffer=w}
const cur=window.sunOpenCurrentClientOffer;if(typeof cur==='function'&&!cur.__sunV1764){const w=function(...args){setActiveOffer(currentDraftId());return cur.apply(this,args)};w.__sunV1764=true;window.sunOpenCurrentClientOffer=w}
}
const menuSvg='<svg class="sun-v1764-menu-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 17.5h16"/><path d="M6 16.5a6 6 0 0 1 12 0"/><path d="M12 10V7.5"/><path d="M10.5 7.5h3"/><path d="M3.5 19.5h17"/></svg>';
function installStyles(){
if($('sunV1764Style'))return;const style=document.createElement('style');style.id='sunV1764Style';style.textContent=`
header nav .nav-menu{display:inline-flex!important;align-items:center;gap:7px}.sun-v1764-menu-icon{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:0 0 auto}
#new .catalog-head .sun-v1764-menu-action{display:inline-flex;align-items:center;gap:7px}.sun-v1764-menu-action .sun-v1764-menu-icon{width:17px;height:17px}
.sun-v1764-offer-template{padding:12px 16px;border-bottom:1px solid #e7e9ec;background:#fbfbfc}.sun-v1764-offer-template-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:9px}.sun-v1764-offer-template-head b{color:#273746}.sun-v1764-offer-template-head small{display:block;color:#7a838c;margin-top:3px}.sun-v1764-offer-template-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}.sun-v1764-offer-template-grid button{border:1px solid #dfe3e7;background:#fff;border-radius:11px;padding:6px;text-align:left;color:#3f4d59}.sun-v1764-offer-template-grid button.on{border-color:var(--g);box-shadow:0 0 0 2px color-mix(in srgb,var(--g) 20%,transparent)}.sun-v1764-offer-template-grid img{display:block;width:100%;aspect-ratio:16/9;object-fit:cover;border-radius:7px;background:#eef1f3}.sun-v1764-offer-template-grid span{display:block;padding:6px 3px 2px;font-size:11px;font-weight:800;line-height:1.2}
#sunOrderCards .sun-order-card[data-sun-auto-completed="1"]{opacity:.88}#sunOrderCards .sun-order-card[data-sun-auto-completed="1"] .sun-order-card-date{filter:saturate(.8)}
@media(max-width:760px){.sun-v1764-offer-template-grid{grid-template-columns:1fr 1fr}}
`;document.head.appendChild(style);
}
function decorateMenuIcon(){
let changed=false;
qa('header nav button.nav-menu,header nav button[data-nav-label="Меню"]').forEach(button=>{if(button.dataset.sunMenuIconV1764==='1')return;button.dataset.sunMenuIconV1764='1';button.innerHTML=`${menuSvg}<span>Меню</span>`;changed=true});
qa('#new .catalog-head button').filter(b=>String(b.textContent||'').trim()==='Меню').forEach(button=>{if(button.dataset.sunMenuIconV1764==='1')return;button.dataset.sunMenuIconV1764='1';button.classList.add('sun-v1764-menu-action');button.innerHTML=`${menuSvg}<span>Меню</span>`;changed=true});
return changed;
}
let autoTimer=0,maintainTimer=0;
function maintain(){patchOrdersRender();patchOfferTemplateApi();patchOfferOpenExports();decorateMenuIcon();if(activeOfferOrderId)$('sunClientOfferModal')?.classList.contains('on')?ensureOfferPicker():clearActiveOffer()}
function startTimers(){
if(!autoTimer)autoTimer=setInterval(()=>{if(!document.hidden)autoCompleteOrders(Date.now(),{silent:false})},AUTO_POLL_MS);
if(!maintainTimer)maintainTimer=setInterval(()=>{if(!document.hidden)maintain()},2000);
}
function onClickCapture(e){
const direct=e.target?.closest?.('[data-sun-offer-id]');if(direct?.dataset?.sunOfferId)setActiveOffer(direct.dataset.sunOfferId);
else if(e.target?.closest?.('#sunClientOfferButton'))setActiveOffer(currentDraftId());
if(e.target?.closest?.('#sunOfferClose'))setTimeout(clearActiveOffer,0);
}
function boot(){
installStyles();patchOrdersRender();patchOfferTemplateApi();patchOfferOpenExports();decorateMenuIcon();autoCompleteOrders(Date.now(),{silent:true});startTimers();
document.addEventListener('click',onClickCapture,true);
document.addEventListener('visibilitychange',()=>{if(!document.hidden){maintain();autoCompleteOrders(Date.now(),{silent:false})}});
window.addEventListener('focus',()=>autoCompleteOrders(Date.now(),{silent:false}));
window.addEventListener('sun:cloud-state-applied',()=>setTimeout(()=>{maintain();autoCompleteOrders(Date.now(),{silent:true})},80));
}
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
window.SunUXFixV1764={
VERSION,AUTO_DELAY_MS,RELEASE_DAY,
autoCompleteOrders,dueAt,isAutoCompleted,classificationDate,
patchOrdersRender,restoreRenderedOrderDates,
activeTemplateId,persistOfferTemplate,ensureOfferPicker,
decorateMenuIcon,
checks:()=>({version:VERSION,autoDelay:AUTO_DELAY_MS,menuIcon:Boolean(document.querySelector('.sun-v1764-menu-icon')),offerPicker:Boolean($('sunOfferTemplateV1764')),renderPatched:Boolean(window.renderOrders?.__sunV1764)})
};
})();

View File

@ -1,8 +1,8 @@
const CACHE='sun-catering-pwa-v68-20260907-v17-6-3-developer-hotfix'; const CACHE='sun-catering-pwa-v69-20260907-v17-6-4-settings-orders-pdf-menu';
const VERSION='20260907-v17-6-3-developer-hotfix'; const VERSION='20260907-v17-6-4-settings-orders-pdf-menu';
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/pdf-engine.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}`,`./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

@ -7,6 +7,11 @@ async function loadModule(page,file,globalName){
await page.addScriptTag({content}); await page.addScriptTag({content});
expect(await page.evaluate(name=>Boolean(window[name]),globalName)).toBeTruthy(); expect(await page.evaluate(name=>Boolean(window[name]),globalName)).toBeTruthy();
} }
async function injectCore(page,file,globalName){
const content=fs.readFileSync(path.join(process.cwd(),'public','core',file),'utf8');
await page.addScriptTag({content});
if(globalName)expect(await page.evaluate(name=>Boolean(window[name]),globalName)).toBeTruthy();
}
test('legacy localStorage payload cannot execute XSS', async ({ page }) => { test('legacy localStorage payload cannot execute XSS', async ({ page }) => {
const pageErrors=[]; page.on('pageerror',e=>pageErrors.push(String(e))); const pageErrors=[]; page.on('pageerror',e=>pageErrors.push(String(e)));
await page.addInitScript(() => { await page.addInitScript(() => {
@ -91,6 +96,67 @@ test('v17.6.3 developer gate bypasses workspace loading and SaaS click is safe',
const args=await page.evaluate(()=>window.__devOpenArgs); const args=await page.evaluate(()=>window.__devOpenArgs);
expect(args).toEqual(['null','null']); expect(args).toEqual(['null','null']);
}); });
test('v17.6.4 auto-completes and fully pays an order one minute after scheduled time', async ({ page }) => {
await page.addInitScript(()=>{
localStorage.setItem('sunOrders',JSON.stringify([{id:764,event:'Тест авто-завершения',date:'2099-09-07',time:'12:00',status:'Новый',prepayment:100,total:1500,lines:[]}]))
});
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
await injectCore(page,'ux-fixes-v1764.js','SunUXFixV1764');
const result=await page.evaluate(()=>{
window.orderTotalValue=()=>1500;
const run=window.SunUXFixV1764.autoCompleteOrders(new Date('2099-09-07T12:01:01').getTime(),{silent:true});
const order=JSON.parse(localStorage.getItem('sunOrders')||'[]').find(x=>String(x.id)==='764');
return {run,prepayment:order?.prepayment,balance:order?.balance,status:order?.status,auto:Boolean(order?.sunAutoCompletedAt),classDate:window.SunUXFixV1764.classificationDate(order,'2099-09-07')};
});
expect(result.run.changed).toBe(1);
expect(result.prepayment).toBe(1500);
expect(result.balance).toBe(0);
expect(result.status).toBe('Отдан заказчику');
expect(result.auto).toBeTruthy();
expect(result.classDate).toBe('0001-01-01');
});
test('v17.6.4 keeps proposal designs per order and renders a styled menu icon', async ({ page }) => {
await page.addInitScript(()=>{
localStorage.setItem('sunOrders',JSON.stringify([
{id:801,event:'Editorial',date:'2099-09-08',time:'12:00',clientOfferSnapshot:{version:8,offerTemplateId:'editorial-grid'}},
{id:802,event:'Emerald',date:'2099-09-08',time:'13:00',clientOfferSnapshot:{version:8,offerTemplateId:'emerald-gold'}}
]));
});
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
await page.evaluate(()=>{
let nav=document.querySelector('header nav');
if(!nav){const header=document.createElement('header');nav=document.createElement('nav');header.appendChild(nav);document.body.prepend(header)}
let menu=nav.querySelector('.nav-menu');if(!menu){menu=document.createElement('button');menu.type='button';menu.className='sun-nav-button nav-menu';menu.dataset.navLabel='Меню';menu.textContent='Меню';nav.appendChild(menu)}
});
await injectCore(page,'ux-fixes-v1764.js','SunUXFixV1764');
await page.waitForFunction(()=>Boolean(document.querySelector('header nav .nav-menu .sun-v1764-menu-icon')),null,{timeout:5000});
const result=await page.evaluate(()=>{
window.SunUXFixV1764.persistOfferTemplate('801','midnight-glass');
const list=JSON.parse(localStorage.getItem('sunOrders')||'[]');
const one=list.find(x=>String(x.id)==='801'),two=list.find(x=>String(x.id)==='802');
return {one:one?.clientOfferTemplateId,snapOne:one?.clientOfferSnapshot?.offerTemplateId,two:two?.clientOfferSnapshot?.offerTemplateId,icon:Boolean(document.querySelector('header nav .nav-menu .sun-v1764-menu-icon'))};
});
expect(result.one).toBe('midnight-glass');
expect(result.snapOne).toBe('midnight-glass');
expect(result.two).toBe('emerald-gold');
expect(result.icon).toBeTruthy();
});
test('Developer Console memory refresh is bounded and does not react to its own DOM update', async ({ page }) => {
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
const performance=fs.readFileSync(path.join(process.cwd(),'public','core','performance.js'),'utf8');
await page.evaluate(()=>{
document.body.innerHTML='<section id="sun-developer-console-v22" class="on"><div id="sunDevBody"></div><span id="sunDevReleaseVersion"></span></section>';
window.__rpcCount=0;
window.SunCloudV2={getClient:()=>({rpc:async()=>{window.__rpcCount++;return {data:{server_size:'35 MB',database_size:'21 MB',storage_size:'14 MB',storage_objects:12}}}})};
});
await page.addScriptTag({content:performance});
await page.waitForFunction(()=>Boolean(window.SunPerformance),null,{timeout:5000});
await page.evaluate(async()=>{await window.SunPerformance.refreshDeveloperMemory(true);await new Promise(r=>setTimeout(r,250));});
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.box).toBe(1);
expect(result.version).toBe('17.6.4');
});
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);
const dims=await page.evaluate(()=>({innerWidth,scrollWidth:document.documentElement.scrollWidth,bodyWidth:document.body.scrollWidth})); const dims=await page.evaluate(()=>({innerWidth,scrollWidth:document.documentElement.scrollWidth,bodyWidth:document.body.scrollWidth}));

View File

@ -3,16 +3,19 @@ import path from 'node:path';
const root=process.cwd(), pub=path.join(root,'public'); 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');
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'); 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');
check(index.includes('core/stability-v1760.css'),'mobile stability stylesheet loaded'); check(index.includes('core/stability-v1760.css'),'mobile stability stylesheet loaded');
check(css.includes('overflow-x:hidden')&&css.includes('.cats'),'mobile overflow guard present'); check(css.includes('overflow-x:hidden')&&css.includes('.cats'),'mobile overflow guard present');
check(!index.includes('offer-gallery-data.js'),'blocking Base64 gallery absent'); 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('v17-6-3-developer-hotfix')&&sw.includes('hotfix-v1763.js'),'service worker cache is v17.6.3'); check(sw.includes('v17-6-4-settings-orders-pdf-menu')&&sw.includes('ux-fixes-v1764.js'),'service worker cache is v17.6.4');
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('records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));enhanceDeveloperMemory()'),'Developer Console refresh is not mutation-driven');
check(performance.includes('ux-fixes-v1764.js')&&performance.includes('SunUXFixV1764'),'v17.6.4 UX module is loaded');
check(performance.includes('hotfix-v1763.js')&&performance.includes('SunHotfixV1763'),'developer/SaaS hotfix is loaded'); check(performance.includes('hotfix-v1763.js')&&performance.includes('SunHotfixV1763'),'developer/SaaS hotfix is loaded');
check(hotfix.includes('patchDeveloperOpen')&&hotfix.includes('enhanceDeveloperGate')&&hotfix.includes('data-saas-admin'),'developer gate and SaaS click hotfix is versioned'); check(hotfix.includes('patchDeveloperOpen')&&hotfix.includes('enhanceDeveloperGate')&&hotfix.includes('data-saas-admin'),'developer gate and SaaS click hotfix is versioned');
check(hotfix.includes('source instanceof HTMLElement')&&hotfix.includes('stopImmediatePropagation'),'SaaS event object cannot reach Developer Console as a nav button'); check(hotfix.includes('source instanceof HTMLElement')&&hotfix.includes('stopImmediatePropagation'),'SaaS event object cannot reach Developer Console as a nav button');
@ -22,6 +25,10 @@ check(ops.includes('sun-menu-editor-v1762')&&ops.includes('Премиум бок
check(ops.includes('showCalendarDay')&&ops.includes('.cal-more'),'calendar overflow panel is versioned'); check(ops.includes('showCalendarDay')&&ops.includes('.cal-more'),'calendar overflow panel is versioned');
check(ops.includes("ROUTE_BASE_KEY='sunRouteBaseV1'")&&ops.includes('saveRouteStart'),'configurable route origin is versioned'); check(ops.includes("ROUTE_BASE_KEY='sunRouteBaseV1'")&&ops.includes('saveRouteStart'),'configurable route origin is versioned');
check(ops.includes('showRouteOrder')&&ops.includes('data-route-open'),'route order modal is versioned'); check(ops.includes('showRouteOrder')&&ops.includes('data-route-open'),'route order modal is versioned');
check(ux.includes("AUTO_DELAY_MS=60*1000")&&ux.includes("order.status='Отдан заказчику'")&&ux.includes('order.prepayment=total'),'orders auto-complete and auto-pay one minute after due time');
check(ux.includes('classificationDate')&&ux.includes('sunAutoCompletedAt'),'auto-completed orders move to completed list without losing original date');
check(ux.includes('persistOfferTemplate')&&ux.includes('clientOfferTemplateId')&&ux.includes('offerTemplateId'),'client proposal template is stored per offer');
check(ux.includes('sun-v1764-menu-icon')&&ux.includes('<svg'),'menu uses a styled SVG icon');
check(fs.existsSync(path.join(root,'supabase/functions/caterium-create-employee/index.ts')),'employee Edge Function source is versioned'); check(fs.existsSync(path.join(root,'supabase/functions/caterium-create-employee/index.ts')),'employee Edge Function source is versioned');
check(!/sb_secret_[A-Za-z0-9_-]{20,}|service_role\s*[:=]\s*["'][A-Za-z0-9._-]{30,}/i.test(index+runtime+performance+ops+hotfix),'no client secret-like token'); check(!/sb_secret_[A-Za-z0-9_-]{20,}|service_role\s*[:=]\s*["'][A-Za-z0-9._-]{30,}/i.test(index+runtime+performance+ops+hotfix+ux),'no client secret-like token');
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'); 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');
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,12 +27,16 @@ 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-3-developer-hotfix')||!sw.includes('hotfix-v1763.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated'); if(!sw.includes('v17-6-4-settings-orders-pdf-menu')||!sw.includes('ux-fixes-v1764.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated');
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('MEMORY_REFRESH_MS=30000')||!performance.includes('MEMORY_TIMEOUT_MS=8000')||!performance.includes('memoryPromise'))fail('Developer Console bounded refresh controls missing');else ok('Developer Console bounded refresh controls present');
if(!performance.includes('ux-fixes-v1764.js')||!performance.includes('SunUXFixV1764'))fail('v17.6.4 UX fix loader missing');else ok('v17.6.4 UX fix loader present');
if(!performance.includes('hotfix-v1763.js')||!performance.includes('SunHotfixV1763'))fail('v17.6.3 hotfix loader missing');else ok('v17.6.3 hotfix loader present'); if(!performance.includes('hotfix-v1763.js')||!performance.includes('SunHotfixV1763'))fail('v17.6.3 hotfix loader missing');else ok('v17.6.3 hotfix loader present');
if(!performance.includes('ops-ux-v1762.js')||!performance.includes('SunOpsUXV1762'))fail('ops UX loader missing');else ok('ops UX loader present'); if(!performance.includes('ops-ux-v1762.js')||!performance.includes('SunOpsUXV1762'))fail('ops UX loader missing');else ok('ops UX loader present');
for(const marker of ['patchDeveloperOpen','enhanceDeveloperGate','data-saas-admin','stopImmediatePropagation','instanceof HTMLElement']) if(!hotfix.includes(marker))fail(`developer/SaaS hotfix marker missing: ${marker}`);else ok(`developer/SaaS hotfix marker: ${marker}`); for(const marker of ['patchDeveloperOpen','enhanceDeveloperGate','data-saas-admin','stopImmediatePropagation','instanceof HTMLElement']) if(!hotfix.includes(marker))fail(`developer/SaaS hotfix marker missing: ${marker}`);else ok(`developer/SaaS hotfix marker: ${marker}`);
for(const marker of ['SUPPORT_POLL_MS=12000','supportReadPermission','sun-menu-editor-v1762','showCalendarDay',"ROUTE_BASE_KEY='sunRouteBaseV1'",'showRouteOrder','routeOpenYandex']) if(!ops.includes(marker)) fail(`ops UX marker missing: ${marker}`); else ok(`ops UX marker: ${marker}`); for(const marker of ['SUPPORT_POLL_MS=12000','supportReadPermission','sun-menu-editor-v1762','showCalendarDay',"ROUTE_BASE_KEY='sunRouteBaseV1'",'showRouteOrder','routeOpenYandex']) if(!ops.includes(marker)) fail(`ops UX marker missing: ${marker}`); else ok(`ops UX marker: ${marker}`);
if(/service_role\s*[:=]\s*['"][A-Za-z0-9._-]{20,}/i.test(ops+hotfix)||/eyJ[a-zA-Z0-9_-]{30,}/.test(ops+hotfix))fail('possible secret in ops/hotfix module');else ok('ops/hotfix modules have no hard-coded secret'); for(const marker of ["AUTO_DELAY_MS=60*1000","order.prepayment=total","order.status='Отдан заказчику'",'sunAutoCompletedAt','classificationDate','persistOfferTemplate','clientOfferTemplateId','offerTemplateId','sun-v1764-menu-icon']) if(!ux.includes(marker))fail(`v17.6.4 UX marker missing: ${marker}`);else ok(`v17.6.4 UX marker: ${marker}`);
if(/service_role\s*[:=]\s*['"][A-Za-z0-9._-]{20,}/i.test(ops+hotfix+ux)||/eyJ[a-zA-Z0-9_-]{30,}/.test(ops+hotfix+ux))fail('possible secret in ops/hotfix/ux module');else ok('ops/hotfix/ux modules have no hard-coded secret');
if(process.exitCode)process.exit(process.exitCode); if(process.exitCode)process.exit(process.exitCode);