Caterium v17.6.6 — proposal templates and Menu icon

Fix Menu sidebar icon alignment and make all four client proposal templates structurally distinct in preview and PDF.
This commit is contained in:
pavlov346346-source 2026-09-08 07:01:35 +03:00 committed by GitHub
parent 71d9cdc92e
commit cd776d5647
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 97 additions and 36 deletions

View File

@ -1,6 +1,6 @@
{ {
"app": "Caterium", "app": "Caterium",
"version": "v17.6.5", "version": "v17.6.6",
"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": "v70-20260908-v17-6-5-stability", "pwaCache": "v71-20260908-v17-6-6-offer-templates",
"fullOfferDescriptions": true, "fullOfferDescriptions": true,
"dynamicOfferRows": true, "dynamicOfferRows": true,
"pdfOfferDescriptionFix": true, "pdfOfferDescriptionFix": true,
@ -99,7 +99,7 @@
"catalogCompositionTildaEndpoint": "getproduct", "catalogCompositionTildaEndpoint": "getproduct",
"catalogCompositionPremiumForceRefresh": true, "catalogCompositionPremiumForceRefresh": true,
"catalogCompositionCacheRequiresPremium": true, "catalogCompositionCacheRequiresPremium": true,
"notes": "Stability hardening: cache-busting corrected from v17.6.0, cloud RPC timeouts and capped conflict retries, deduplicated network error logging with backoff, lightweight support refresh, calmer Developer/background polling.", "notes": "Four proposal templates now keep the per-order choice and use distinct PDF compositions. Menu sidebar icon uses the native mask system without the yellow square.",
"catalogPhotoSources": 113, "catalogPhotoSources": 113,
"catalogPhotosStoredInCatalog": true, "catalogPhotosStoredInCatalog": true,
"catalogLegacyPhotosInCatalog": 60, "catalogLegacyPhotosInCatalog": 60,
@ -237,7 +237,7 @@
"signupTrial": "14-day Full", "signupTrial": "14-day Full",
"developerMfaInputSelectorFixed": true, "developerMfaInputSelectorFixed": true,
"developerMfaRepeatedValidationToastsFixed": true, "developerMfaRepeatedValidationToastsFixed": true,
"release": "20260908-v17-6-5-stability", "release": "20260908-v17-6-6-offer-templates",
"registrationFlow": "email-password-confirm-company-auto-login", "registrationFlow": "email-password-confirm-company-auto-login",
"emailConfirmationRequired": false, "emailConfirmationRequired": false,
"employeeInviteLinks": false, "employeeInviteLinks": false,
@ -324,5 +324,10 @@
"supportRefreshIntervalMs": 20000, "supportRefreshIntervalMs": 20000,
"indexCacheBustCurrent": true, "indexCacheBustCurrent": true,
"backgroundImageMutationBatching": true, "backgroundImageMutationBatching": true,
"opsBootWaitBounded": true "opsBootWaitBounded": true,
"offerTemplates": 4,
"offerTemplatePerOrder": true,
"offerTemplateStructuralPreviews": true,
"offerPdfDistinctLayouts": true,
"menuNavMaskFixed": true
} }

View File

@ -0,0 +1,8 @@
Caterium v17.6.6 — Proposal templates and Menu icon
Date: 2026-09-08
- Removed the yellow square and duplicate icon next to Menu. The item now uses the same 20px masked sidebar icon system as Stock and Routes.
- Fixed per-order proposal template selection being overwritten by the global template during rendering.
- Four PDF templates now use different section composition, not only different colors.
- Template pickers use structural mini-previews matching the intended PDF hierarchy.
- PWA cache v71 / 20260908-v17-6-6-offer-templates.

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "caterium-app", "name": "caterium-app",
"version": "17.6.5", "version": "17.6.6",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "caterium-app", "name": "caterium-app",
"version": "17.6.5", "version": "17.6.6",
"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,7 +1,7 @@
{ {
"name": "caterium-app", "name": "caterium-app",
"private": true, "private": true,
"version": "17.6.5", "version": "17.6.6",
"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", "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",

View File

@ -1170,7 +1170,7 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
function offerRenderMessage(text='Обновляю предложение…'){const host=$('sunClientOfferPreview');if(host)host.innerHTML=`<div class="sun-offer-rendering">${esc(text)}</div>`} function offerRenderMessage(text='Обновляю предложение…'){const host=$('sunClientOfferPreview');if(host)host.innerHTML=`<div class="sun-offer-rendering">${esc(text)}</div>`}
async function renderOffer(snapshot){ async function renderOffer(snapshot){
const renderToken=++offerRenderSeq;currentOffer.snapshot=snapshot;currentOffer.pdfBlob=null;currentOffer.pdfPromise=null;currentOffer.pages=null;offerRenderMessage();const pdfBtn=$('sunOfferPdf');if(pdfBtn){pdfBtn.disabled=true;pdfBtn.textContent='Готовлю просмотр…'} const renderToken=++offerRenderSeq;currentOffer.snapshot=snapshot;currentOffer.pdfBlob=null;currentOffer.pdfPromise=null;currentOffer.pages=null;offerRenderMessage();const pdfBtn=$('sunOfferPdf');if(pdfBtn){pdfBtn.disabled=true;pdfBtn.textContent='Готовлю просмотр…'}
const prepared=await preparedSnapshot(snapshot);prepared.offerTemplateId=offerTemplateId(); const prepared=await preparedSnapshot(snapshot);const explicitTemplate=String(snapshot?.offerTemplateId||prepared?.offerTemplateId||'').trim();prepared.offerTemplateId=OFFER_TEMPLATE_IDS.has(explicitTemplate)?explicitTemplate:offerTemplateId();
const pdfPrepared=await preparedPdfSnapshot(prepared),pages=await renderOfferPdfPages(pdfPrepared); const pdfPrepared=await preparedPdfSnapshot(prepared),pages=await renderOfferPdfPages(pdfPrepared);
if(renderToken!==offerRenderSeq)return null; if(renderToken!==offerRenderSeq)return null;
currentOffer.snapshot=snapshot;currentOffer.prepared=prepared;currentOffer.pdfPrepared=pdfPrepared;currentOffer.pages=pages;currentOffer.renderToken=renderToken; currentOffer.snapshot=snapshot;currentOffer.prepared=prepared;currentOffer.pdfPrepared=pdfPrepared;currentOffer.pages=pages;currentOffer.renderToken=renderToken;
@ -1530,7 +1530,11 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
else{const imgs=finalGallery.slice(0,2),cellW=(W-2*M-gap)/2;if(imgs[0])pdfDrawCover(ctx,imgs[0],M,y,cellW,available,14);if(imgs[1])pdfDrawCover(ctx,imgs[1],M+cellW+gap,y,cellW,available,14)} else{const imgs=finalGallery.slice(0,2),cellW=(W-2*M-gap)/2;if(imgs[0])pdfDrawCover(ctx,imgs[0],M,y,cellW,available,14);if(imgs[1])pdfDrawCover(ctx,imgs[1],M+cellW+gap,y,cellW,available,14)}
y=bottom; y=bottom;
} }
newPage();brandHeader();hero();sellingBlock();drawItems();summary();if(id==='editorial-grid'){pricing();infoCards()}else{infoCards();pricing()}footerNoteBlock();finalGalleryFill(); newPage();
if(id==='editorial-grid'){brandHeader();hero();drawItems();summary();pricing();sellingBlock();infoCards();finalGalleryFill();footerNoteBlock();}
else if(id==='midnight-glass'){brandHeader();hero();sellingBlock();summary();infoCards();drawItems();pricing();finalGalleryFill();footerNoteBlock();}
else if(id==='emerald-gold'){brandHeader();hero();summary();sellingBlock();infoCards();pricing();drawItems();footerNoteBlock();finalGalleryFill();}
else{brandHeader();hero();sellingBlock();drawItems();summary();infoCards();pricing();footerNoteBlock();finalGalleryFill();}
canvases.forEach((c,i)=>{const x=c.getContext('2d');x.setTransform(SCALE,0,0,SCALE,0,0);x.save();x.fillStyle=FOOT;x.fillRect(0,H-52,W,52);x.strokeStyle=LINE;x.lineWidth=1;x.beginPath();x.moveTo(0,H-52.5);x.lineTo(W,H-52.5);x.stroke();pdfText(x,'Солнце Кейтеринг · Санкт-Петербург',M,H-36,620,22,{font:'12px Arial',color:MUTED,maxLines:1});pdfText(x,`${i+1} / ${canvases.length}`,W-M,H-36,100,22,{font:'12px Arial',color:MUTED,align:'right',maxLines:1});x.restore()}); canvases.forEach((c,i)=>{const x=c.getContext('2d');x.setTransform(SCALE,0,0,SCALE,0,0);x.save();x.fillStyle=FOOT;x.fillRect(0,H-52,W,52);x.strokeStyle=LINE;x.lineWidth=1;x.beginPath();x.moveTo(0,H-52.5);x.lineTo(W,H-52.5);x.stroke();pdfText(x,'Солнце Кейтеринг · Санкт-Петербург',M,H-36,620,22,{font:'12px Arial',color:MUTED,maxLines:1});pdfText(x,`${i+1} / ${canvases.length}`,W-M,H-36,100,22,{font:'12px Arial',color:MUTED,align:'right',maxLines:1});x.restore()});
return canvases; return canvases;
} }
@ -4096,10 +4100,10 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
const KEY='sunOfferTemplateV1'; const KEY='sunOfferTemplateV1';
const DEFAULT_ID='light'; const DEFAULT_ID='light';
const TEMPLATES=[ const TEMPLATES=[
{id:'light',name:'Светлый фирменный',desc:'Белый фон, фирменный зелёный и золото. Основной дизайн.',thumb:'offer-templates/thumb-light.jpg'}, {id:'light',name:'Светлый фирменный',desc:'Классическая A4-композиция: обложка, карточки меню, итог и условия.',thumb:'offer-templates/thumb-light.jpg'},
{id:'editorial-grid',name:'Солнце Editorial',desc:'Макет по референсу: крупный первый экран, компактное меню, фотосетка и итоги на одной визуальной системе.',thumb:'offer-templates/thumb-editorial-grid.jpg'}, {id:'editorial-grid',name:'Солнце Editorial',desc:'Журнальная композиция: крупный первый экран, меню-сетка, фотоблоки и редакционная подача.',thumb:'offer-templates/thumb-editorial-grid.jpg'},
{id:'midnight-glass',name:'Midnight Glass',desc:'Двухколоночный макет: меню списком слева, реальные фото боксов мозаикой справа.',thumb:'offer-templates/thumb-midnight-glass.jpg'}, {id:'midnight-glass',name:'Midnight Glass',desc:'Тёмная презентация: стеклянные панели, итог до меню и контрастная двухколоночная композиция.',thumb:'offer-templates/thumb-midnight-glass.jpg'},
{id:'emerald-gold',name:'Emerald Gold',desc:'Премиальная композиция: золотой итог в первом экране, меню слева и фотоколлаж справа.',thumb:'offer-templates/thumb-emerald-gold.jpg'} {id:'emerald-gold',name:'Emerald Gold',desc:'Премиальный буклет: золотой итог на первом экране, условия и меню как финальная часть.',thumb:'offer-templates/thumb-emerald-gold.jpg'}
]; ];
const esc=window.SunSafe.escapeHTML; const esc=window.SunSafe.escapeHTML;
@ -4140,11 +4144,17 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
`; `;
document.head.appendChild(style); document.head.appendChild(style);
function offerTemplateMini(id){
if(id==='editorial-grid')return '<i class="mini-head"></i><i class="mini-editorial-hero"></i><i class="mini-editorial-stack a"></i><i class="mini-editorial-stack b"></i><i class="mini-editorial-grid"></i>';
if(id==='midnight-glass')return '<i class="mini-head"></i><i class="mini-midnight-hero"></i><i class="mini-midnight-total"></i><i class="mini-midnight-left"></i><i class="mini-midnight-right"></i>';
if(id==='emerald-gold')return '<i class="mini-head"></i><i class="mini-emerald-hero"></i><i class="mini-emerald-gold"></i><i class="mini-emerald-info"></i><i class="mini-emerald-menu"></i>';
return '<i class="mini-head"></i><i class="mini-light-hero"></i><i class="mini-light-row a"></i><i class="mini-light-row b"></i><i class="mini-light-row c"></i><i class="mini-light-total"></i>';
}
function cardHtml(){ function cardHtml(){
const cur=selected(); const cur=selected();
return `<h2>Шаблон предложения клиенту</h2> return `<h2>Шаблон предложения клиенту</h2>
<p class="hint">Выберите внешний вид, который будет использоваться в «Предложении клиенту» и в PDF. Реальные фотографии боксов и данные заказа остаются одинаковыми меняется только дизайн.</p> <p class="hint">Выберите внешний вид, который будет использоваться в «Предложении клиенту» и в PDF. Реальные фотографии боксов и данные заказа остаются одинаковыми меняется только дизайн.</p>
<div class="sun-offer-template-grid">${TEMPLATES.map(t=>`<article class="sun-offer-template-option ${t.id===state.id?'on':''}" data-offer-template="${esc(t.id)}" tabindex="0" role="radio" aria-checked="${t.id===state.id?'true':'false'}"><img class="sun-offer-template-thumb" src="${esc(t.thumb)}" alt="${esc(t.name)}"><div class="sun-offer-template-info"><strong>${esc(t.name)}</strong><span class="sun-offer-template-check">✓</span><p>${esc(t.desc)}</p></div></article>`).join('')}</div> <div class="sun-offer-template-grid">${TEMPLATES.map(t=>`<article class="sun-offer-template-option ${t.id===state.id?'on':''}" data-offer-template="${esc(t.id)}" tabindex="0" role="radio" aria-checked="${t.id===state.id?'true':'false'}"><div class="sun-offer-template-thumb sun-offer-template-mini sun-offer-template-mini-${esc(t.id)}" data-template-mini="${esc(t.id)}" aria-hidden="true">${offerTemplateMini(t.id)}</div><div class="sun-offer-template-info"><strong>${esc(t.name)}</strong><span class="sun-offer-template-check">✓</span><p>${esc(t.desc)}</p></div></article>`).join('')}</div>
<div class="sun-offer-template-toolbar"><span class="current">Сейчас выбран: <b>${esc(cur.name)}</b></span><button type="button" class="outline" data-offer-template-reset>Вернуть светлый фирменный</button></div>`; <div class="sun-offer-template-toolbar"><span class="current">Сейчас выбран: <b>${esc(cur.name)}</b></span><button type="button" class="outline" data-offer-template-reset>Вернуть светлый фирменный</button></div>`;
} }
function bind(card){ function bind(card){

View File

@ -1,7 +1,7 @@
(()=>{ (()=>{
'use strict'; 'use strict';
const VERSION='17.6.5'; const VERSION='17.6.6';
const RELEASE='20260908-v17-6-5-stability'; const RELEASE='20260908-v17-6-6-offer-templates';
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;

View File

@ -2,7 +2,7 @@
'use strict'; 'use strict';
if(window.SunUXFixV1764)return; if(window.SunUXFixV1764)return;
const VERSION='17.6.4'; const VERSION='17.6.6';
const RELEASE_DAY='2026-09-07'; const RELEASE_DAY='2026-09-07';
const AUTO_DELAY_MS=60*1000; const AUTO_DELAY_MS=60*1000;
const AUTO_POLL_MS=15000; const AUTO_POLL_MS=15000;
@ -159,8 +159,14 @@
const order=orderById(activeOfferOrderId);if(!order||storedTemplateId(order))return storedTemplateId(order); const order=orderById(activeOfferOrderId);if(!order||storedTemplateId(order))return storedTemplateId(order);
const id=globalTemplateId();persistOfferTemplate(activeOfferOrderId,id);return id; const id=globalTemplateId();persistOfferTemplate(activeOfferOrderId,id);return id;
} }
function offerTemplateMini(id){
if(id==='editorial-grid')return '<i class="mini-head"></i><i class="mini-editorial-hero"></i><i class="mini-editorial-stack a"></i><i class="mini-editorial-stack b"></i><i class="mini-editorial-grid"></i>';
if(id==='midnight-glass')return '<i class="mini-head"></i><i class="mini-midnight-hero"></i><i class="mini-midnight-total"></i><i class="mini-midnight-left"></i><i class="mini-midnight-right"></i>';
if(id==='emerald-gold')return '<i class="mini-head"></i><i class="mini-emerald-hero"></i><i class="mini-emerald-gold"></i><i class="mini-emerald-info"></i><i class="mini-emerald-menu"></i>';
return '<i class="mini-head"></i><i class="mini-light-hero"></i><i class="mini-light-row a"></i><i class="mini-light-row b"></i><i class="mini-light-row c"></i><i class="mini-light-total"></i>';
}
function pickerHtml(){ 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>`; const current=activeTemplateId();return `<div class="sun-v1764-offer-template-head"><div><b>Оформление PDF</b><small>Шаблон закрепляется за этим предложением. Миниатюра повторяет структуру PDF.</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':''}"><div class="sun-offer-template-mini sun-offer-template-mini-${esc(t.id)}" data-template-mini="${esc(t.id)}" aria-hidden="true">${offerTemplateMini(t.id)}</div><span>${esc(t.name||t.id)}</span></button>`).join('')}</div>`;
} }
function ensureOfferPicker(){ function ensureOfferPicker(){
patchOfferTemplateApi();const modal=$('sunClientOfferModal'),dialog=modal?.querySelector('.dialog');if(!dialog||!activeOfferOrderId)return false; patchOfferTemplateApi();const modal=$('sunClientOfferModal'),dialog=modal?.querySelector('.dialog');if(!dialog||!activeOfferOrderId)return false;
@ -182,13 +188,23 @@
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} 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} #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} .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}
header nav .nav-menu{--sun-icon:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.9%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M4%2018h16M6%2017a6%206%200%200%201%2012%200M12%2011V8M10.5%208h3M3%2020h18%22%2F%3E%3C%2Fsvg%3E")!important;display:flex!important;align-items:center!important;gap:0!important}
.sun-enterprise-sidebar header nav .nav-menu>.sun-v1764-menu-icon{display:none!important}
.sun-offer-template-mini{position:relative;display:block;width:100%;aspect-ratio:16/10;overflow:hidden;border-radius:8px;background:#fff;border:1px solid #e3e5e7;box-sizing:border-box}.sun-offer-template-mini i{position:absolute;display:block;border-radius:2px}.sun-offer-template-mini .mini-head{left:7%;right:7%;top:7%;height:6%;background:#c8b17a}.sun-offer-template-mini-light{background:#fbfaf6}.mini-light-hero{left:7%;right:7%;top:18%;height:24%;background:linear-gradient(90deg,#e9eee7 0 45%,#bdc8bb 45%)}.mini-light-row{left:7%;right:7%;height:10%;background:#eff1ed;border:1px solid #d8dcd5}.mini-light-row.a{top:47%}.mini-light-row.b{top:59%}.mini-light-row.c{top:71%}.mini-light-total{left:55%;right:7%;bottom:7%;height:12%;background:#d7e4d5}
.sun-offer-template-mini-editorial-grid{background:#fbfaf6}.mini-editorial-hero{left:7%;top:18%;width:45%;height:35%;background:#dfe8df}.mini-editorial-stack{left:55%;right:7%;height:16%;background:#b8c9b7}.mini-editorial-stack.a{top:18%}.mini-editorial-stack.b{top:37%}.mini-editorial-grid{left:7%;right:7%;bottom:8%;height:32%;background:repeating-linear-gradient(90deg,#ece7dc 0 22%,transparent 22% 26%)}
.sun-offer-template-mini-midnight-glass{background:#071621;border-color:#294a5b}.sun-offer-template-mini-midnight-glass .mini-head{background:#d5aa56}.mini-midnight-hero{left:7%;right:7%;top:18%;height:30%;background:linear-gradient(90deg,#12313b 0 57%,#31515b 57%)}.mini-midnight-total{left:7%;right:7%;top:53%;height:12%;background:#d5aa56}.mini-midnight-left{left:7%;width:54%;bottom:8%;height:22%;background:#0b2633;border:1px solid #315263}.mini-midnight-right{right:7%;width:27%;bottom:8%;height:22%;background:repeating-linear-gradient(0deg,#284856 0 30%,#102735 30% 34%)}
.sun-offer-template-mini-emerald-gold{background:#061318;border-color:#314a4c}.sun-offer-template-mini-emerald-gold .mini-head{background:#d7ac55}.mini-emerald-hero{left:7%;right:7%;top:18%;height:38%;background:linear-gradient(90deg,#0c2d29 0 60%,#31534b 60%)}.mini-emerald-gold{left:10%;width:42%;top:43%;height:10%;background:#d7ac55}.mini-emerald-info{left:7%;right:57%;bottom:8%;height:27%;background:#0b2526;border:1px solid #67572f}.mini-emerald-menu{left:46%;right:7%;bottom:8%;height:27%;background:repeating-linear-gradient(0deg,#102c2d 0 17%,#67572f 17% 19%)}
.sun-offer-template-option .sun-offer-template-mini{width:100%;aspect-ratio:16/9;border-radius:10px}.sun-offer-template-thumb.sun-offer-template-mini{height:auto;object-fit:initial}
#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)} #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}} @media(max-width:760px){.sun-v1764-offer-template-grid{grid-template-columns:1fr 1fr}}
`;document.head.appendChild(style); `;document.head.appendChild(style);
} }
function decorateMenuIcon(){ function decorateMenuIcon(){
let changed=false; 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('header nav button.nav-menu,header nav button[data-nav-label="Меню"]').forEach(button=>{
button.dataset.sunMenuIconV1766='1';button.querySelectorAll('.sun-v1764-menu-icon').forEach(x=>x.remove());
let label=button.querySelector('span');if(!label){label=document.createElement('span');label.textContent='Меню';button.textContent='';button.appendChild(label)}else label.textContent='Меню';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}); 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; return changed;
} }

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
const CACHE='sun-catering-pwa-v70-20260908-v17-6-5-stability'; const CACHE='sun-catering-pwa-v71-20260908-v17-6-6-offer-templates';
const VERSION='20260908-v17-6-5-stability'; const VERSION='20260908-v17-6-6-offer-templates';
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}`,`./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}`,

View File

@ -129,17 +129,18 @@ test('v17.6.4 keeps proposal designs per order and renders a styled menu icon',
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)} 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 injectCore(page,'ux-fixes-v1764.js','SunUXFixV1764');
await page.waitForFunction(()=>Boolean(document.querySelector('header nav .nav-menu .sun-v1764-menu-icon')),null,{timeout:5000}); await page.waitForFunction(()=>document.querySelector('header nav .nav-menu')?.dataset?.sunMenuIconV1766==='1',null,{timeout:5000});
const result=await page.evaluate(()=>{ const result=await page.evaluate(()=>{
window.SunUXFixV1764.persistOfferTemplate('801','midnight-glass'); window.SunUXFixV1764.persistOfferTemplate('801','midnight-glass');
const list=JSON.parse(localStorage.getItem('sunOrders')||'[]'); const list=JSON.parse(localStorage.getItem('sunOrders')||'[]');
const one=list.find(x=>String(x.id)==='801'),two=list.find(x=>String(x.id)==='802'); 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'))}; const menu=document.querySelector('header nav .nav-menu');return {one:one?.clientOfferTemplateId,snapOne:one?.clientOfferSnapshot?.offerTemplateId,two:two?.clientOfferSnapshot?.offerTemplateId,icon:menu?.dataset?.sunMenuIconV1766==='1',inlineIcon:menu?.querySelectorAll('.sun-v1764-menu-icon').length||0};
}); });
expect(result.one).toBe('midnight-glass'); expect(result.one).toBe('midnight-glass');
expect(result.snapOne).toBe('midnight-glass'); expect(result.snapOne).toBe('midnight-glass');
expect(result.two).toBe('emerald-gold'); expect(result.two).toBe('emerald-gold');
expect(result.icon).toBeTruthy(); expect(result.icon).toBeTruthy();
expect(result.inlineIcon).toBe(0);
}); });
test('Developer Console memory refresh is bounded and does not react to its own DOM update', async ({ page }) => { 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'}); await page.goto('/index.html',{waitUntil:'domcontentloaded'});
@ -155,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.5'); expect(result.version).toBe('17.6.6');
}); });
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);
@ -190,4 +191,21 @@ test('v17.6.5 developer hotfix does not poll admin every second', async ({ page
await injectCore(page,'hotfix-v1763.js','SunHotfixV1763'); await injectCore(page,'hotfix-v1763.js','SunHotfixV1763');
await page.waitForTimeout(2600); await page.waitForTimeout(2600);
expect(await page.evaluate(()=>window.__devChecks)).toBeLessThanOrEqual(2); expect(await page.evaluate(()=>window.__devChecks)).toBeLessThanOrEqual(2);
}); });
test('v17.6.6 Menu sidebar icon uses native mask and has no duplicate inline SVG', async ({ page }) => {
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
await page.evaluate(()=>{let nav=document.querySelector('header nav');if(!nav){const h=document.createElement('header');nav=document.createElement('nav');h.appendChild(nav);document.body.prepend(h)}let b=nav.querySelector('.nav-menu');if(!b){b=document.createElement('button');b.className='sun-nav-button nav-menu';b.dataset.navLabel='Меню';b.textContent='Меню';nav.appendChild(b)}});
await injectCore(page,'ux-fixes-v1764.js','SunUXFixV1764');
await page.waitForFunction(()=>document.querySelector('.nav-menu')?.dataset?.sunMenuIconV1766==='1',null,{timeout:5000});
const r=await page.evaluate(()=>{const b=document.querySelector('.nav-menu');return {inline:b.querySelectorAll('.sun-v1764-menu-icon').length,marker:b.dataset.sunMenuIconV1766,label:b.textContent.trim()}});
const uxSource=fs.readFileSync(path.join(process.cwd(),'public','core','ux-fixes-v1764.js'),'utf8');
expect(r.inline).toBe(0);expect(r.marker).toBe('1');expect(r.label).toContain('Меню');expect(uxSource).toContain('--sun-icon:url(');
});
test('v17.6.6 proposal templates preserve selection and expose four structural previews', async () => {
const runtimeSource=fs.readFileSync(path.join(process.cwd(),'public','app-runtime.js'),'utf8');
const uxSource=fs.readFileSync(path.join(process.cwd(),'public','core','ux-fixes-v1764.js'),'utf8');
expect(runtimeSource).toContain("OFFER_TEMPLATE_IDS.has(explicitTemplate)");
for(const marker of ['mini-light-hero','mini-editorial-hero','mini-midnight-total','mini-emerald-gold'])expect(runtimeSource+uxSource).toContain(marker);
});

View File

@ -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('v17-6-5-stability')&&sw.includes('ux-fixes-v1764.js'),'service worker cache is v17.6.5'); check(sw.includes('v17-6-6-offer-templates')&&sw.includes('ux-fixes-v1764.js'),'service worker cache is v17.6.6');
check(index.includes('20260908-v17-6-5-stability')&&!index.includes('20260907-v17-6-0-stability-security'),'index cache-busting points to v17.6.5'); check(index.includes('20260908-v17-6-6-offer-templates')&&!index.includes('20260907-v17-6-0-stability-security'),'index cache-busting points to v17.6.6');
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');
@ -33,17 +33,20 @@ check(ops.includes('showRouteOrder')&&ops.includes('data-route-open'),'route ord
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("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('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('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(ux.includes('sunMenuIconV1766')&&ux.includes('--sun-icon:url('),'menu uses the native sidebar mask icon without a duplicate square');
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+ux),'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');
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('v17-6-5'),'release manifest points to current PWA cache'); check(String(releaseManifest.pwaCache||'').includes('v17-6-6'),'release manifest points to current PWA cache');
check(['17.6.2','17.6.3','17.6.4','17.6.5'].every(v=>fs.existsSync(path.join(root,`docs/releases/V${v}-CHANGES.txt`))),'release notes exist through v17.6.5'); check(['17.6.2','17.6.3','17.6.4','17.6.5','17.6.6'].every(v=>fs.existsSync(path.join(root,`docs/releases/V${v}-CHANGES.txt`))),'release notes exist through v17.6.6');
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');
check(runtime.includes('DEV_ADMIN_TTL_MS=30000')&&hotfix.includes('checkPlatformAdmin?.(false)')&&hotfix.includes('},10000);'),'developer access checks are throttled'); check(runtime.includes('DEV_ADMIN_TTL_MS=30000')&&hotfix.includes('checkPlatformAdmin?.(false)')&&hotfix.includes('},10000);'),'developer access checks are throttled');
check(performance.includes('pendingImageRoots')&&performance.includes('queueImageScan')&&ux.includes('},5000);'),'background DOM maintenance is batched/throttled'); check(performance.includes('pendingImageRoots')&&performance.includes('queueImageScan')&&ux.includes('},5000);'),'background DOM maintenance is batched/throttled');
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')"),'four proposal templates use distinct PDF layout sequences');
check(runtime.includes('data-template-mini')&&ux.includes('data-template-mini'),'global and per-offer selectors show structural PDF previews');
if(bad)process.exit(1); if(bad)process.exit(1);

View File

@ -27,14 +27,14 @@ 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-5-stability')||!sw.includes('ux-fixes-v1764.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated to v17.6.5'); if(!sw.includes('v17-6-6-offer-templates')||!sw.includes('ux-fixes-v1764.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated to v17.6.6');
if(html.includes('20260907-v17-6-0-stability-security')||!html.includes('20260908-v17-6-5-stability'))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-6-offer-templates'))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');
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('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('pendingImageRoots')||!performance.includes('queueImageScan'))fail('batched image mutation scanning missing');else ok('image mutation scanning is batched'); if(!performance.includes('pendingImageRoots')||!performance.includes('queueImageScan'))fail('batched image mutation scanning missing');else ok('image mutation scanning is batched');
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('ux-fixes-v1764.js')||!performance.includes('SunUXFixV1764'))fail('UX fix loader missing');else ok('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}`);
@ -42,5 +42,6 @@ for(const marker of ['SUPPORT_POLL_MS=20000','supportReadPermission','refreshSup
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}`); 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}`);
for(const marker of ['CLOUD_RPC_TIMEOUT_MS=12000','CLOUD_CONFLICT_MAX_RETRIES=4','refreshSupportWorkspace',"const VERSION = '17.6.5'",'ERROR_DEDUPE_MS=5*60*1000','DEV_ADMIN_TTL_MS=30000']) if(!runtime.includes(marker))fail(`v17.6.5 stability marker missing: ${marker}`);else ok(`v17.6.5 stability marker: ${marker}`); for(const marker of ['CLOUD_RPC_TIMEOUT_MS=12000','CLOUD_CONFLICT_MAX_RETRIES=4','refreshSupportWorkspace',"const VERSION = '17.6.5'",'ERROR_DEDUPE_MS=5*60*1000','DEV_ADMIN_TTL_MS=30000']) if(!runtime.includes(marker))fail(`v17.6.5 stability marker missing: ${marker}`);else ok(`v17.6.5 stability marker: ${marker}`);
if(!hotfix.includes('checkPlatformAdmin?.(false)')||!hotfix.includes('},10000);'))fail('Developer fallback polling is still aggressive');else ok('Developer fallback polling is throttled'); if(!hotfix.includes('checkPlatformAdmin?.(false)')||!hotfix.includes('},10000);'))fail('Developer fallback polling is still aggressive');else ok('Developer fallback polling is throttled');
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(/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(/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);