diff --git a/docs/release-manifest.json b/docs/release-manifest.json index 2b4c489..54158c5 100644 --- a/docs/release-manifest.json +++ b/docs/release-manifest.json @@ -1,6 +1,6 @@ { "app": "Caterium", - "version": "v17.6.5", + "version": "v17.6.6", "channel": "production", "schema": 17, "legacyStateKept": true, @@ -11,7 +11,7 @@ "serverReady": true, "workspaceAutoDiscovery": true, "invitesTemporarilyDisabled": false, - "pwaCache": "v70-20260908-v17-6-5-stability", + "pwaCache": "v71-20260908-v17-6-6-offer-templates", "fullOfferDescriptions": true, "dynamicOfferRows": true, "pdfOfferDescriptionFix": true, @@ -99,7 +99,7 @@ "catalogCompositionTildaEndpoint": "getproduct", "catalogCompositionPremiumForceRefresh": 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, "catalogPhotosStoredInCatalog": true, "catalogLegacyPhotosInCatalog": 60, @@ -237,7 +237,7 @@ "signupTrial": "14-day Full", "developerMfaInputSelectorFixed": true, "developerMfaRepeatedValidationToastsFixed": true, - "release": "20260908-v17-6-5-stability", + "release": "20260908-v17-6-6-offer-templates", "registrationFlow": "email-password-confirm-company-auto-login", "emailConfirmationRequired": false, "employeeInviteLinks": false, @@ -324,5 +324,10 @@ "supportRefreshIntervalMs": 20000, "indexCacheBustCurrent": true, "backgroundImageMutationBatching": true, - "opsBootWaitBounded": true + "opsBootWaitBounded": true, + "offerTemplates": 4, + "offerTemplatePerOrder": true, + "offerTemplateStructuralPreviews": true, + "offerPdfDistinctLayouts": true, + "menuNavMaskFixed": true } diff --git a/docs/releases/V17.6.6-CHANGES.txt b/docs/releases/V17.6.6-CHANGES.txt new file mode 100644 index 0000000..33a910f --- /dev/null +++ b/docs/releases/V17.6.6-CHANGES.txt @@ -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. diff --git a/package-lock.json b/package-lock.json index b96c716..23fb380 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "caterium-app", - "version": "17.6.5", + "version": "17.6.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "caterium-app", - "version": "17.6.5", + "version": "17.6.6", "devDependencies": { "@playwright/test": "^1.51.0", "http-server": "^14.1.1", diff --git a/package.json b/package.json index 8012090..1064291 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "caterium-app", "private": true, - "version": "17.6.5", + "version": "17.6.6", "type": "module", "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", diff --git a/public/app-runtime.js b/public/app-runtime.js index 10b22f5..8ca7572 100644 --- a/public/app-runtime.js +++ b/public/app-runtime.js @@ -1170,7 +1170,7 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс function offerRenderMessage(text='Обновляю предложение…'){const host=$('sunClientOfferPreview');if(host)host.innerHTML=`
${esc(text)}
`} 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 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); if(renderToken!==offerRenderSeq)return null; 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)} 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()}); return canvases; } @@ -4096,10 +4100,10 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс const KEY='sunOfferTemplateV1'; const DEFAULT_ID='light'; const TEMPLATES=[ - {id:'light',name:'Светлый фирменный',desc:'Белый фон, фирменный зелёный и золото. Основной дизайн.',thumb:'offer-templates/thumb-light.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:'emerald-gold',name:'Emerald Gold',desc:'Премиальная композиция: золотой итог в первом экране, меню слева и фотоколлаж справа.',thumb:'offer-templates/thumb-emerald-gold.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:'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'} ]; const esc=window.SunSafe.escapeHTML; @@ -4140,11 +4144,17 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс `; document.head.appendChild(style); + function offerTemplateMini(id){ + if(id==='editorial-grid')return ''; + if(id==='midnight-glass')return ''; + if(id==='emerald-gold')return ''; + return ''; + } function cardHtml(){ const cur=selected(); return `

Шаблон предложения клиенту

Выберите внешний вид, который будет использоваться в «Предложении клиенту» и в PDF. Реальные фотографии боксов и данные заказа остаются одинаковыми — меняется только дизайн.

-
${TEMPLATES.map(t=>``).join('')}
+
${TEMPLATES.map(t=>``).join('')}
Сейчас выбран: ${esc(cur.name)}
`; } function bind(card){ diff --git a/public/core/performance.js b/public/core/performance.js index 57c3604..b179c67 100644 --- a/public/core/performance.js +++ b/public/core/performance.js @@ -1,7 +1,7 @@ (()=>{ 'use strict'; - const VERSION='17.6.5'; - const RELEASE='20260908-v17-6-5-stability'; + const VERSION='17.6.6'; + 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 tune=img=>{ if(!(img instanceof HTMLImageElement)||critical(img))return; diff --git a/public/core/ux-fixes-v1764.js b/public/core/ux-fixes-v1764.js index ba1ebd7..20216e1 100644 --- a/public/core/ux-fixes-v1764.js +++ b/public/core/ux-fixes-v1764.js @@ -2,7 +2,7 @@ 'use strict'; if(window.SunUXFixV1764)return; - const VERSION='17.6.4'; + const VERSION='17.6.6'; const RELEASE_DAY='2026-09-07'; const AUTO_DELAY_MS=60*1000; const AUTO_POLL_MS=15000; @@ -159,8 +159,14 @@ const order=orderById(activeOfferOrderId);if(!order||storedTemplateId(order))return storedTemplateId(order); const id=globalTemplateId();persistOfferTemplate(activeOfferOrderId,id);return id; } + function offerTemplateMini(id){ + if(id==='editorial-grid')return ''; + if(id==='midnight-glass')return ''; + if(id==='emerald-gold')return ''; + return ''; + } function pickerHtml(){ - const current=activeTemplateId();return `
Оформление PDFШаблон закрепляется только за этим предложением.
${templateList().map(t=>``).join('')}
`; + const current=activeTemplateId();return `
Оформление PDFШаблон закрепляется за этим предложением. Миниатюра повторяет структуру PDF.
${templateList().map(t=>``).join('')}
`; } function ensureOfferPicker(){ 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} #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} + 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)} @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}Меню`;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}Меню`;changed=true}); return changed; } diff --git a/public/index.html b/public/index.html index 1544a5c..abcdeca 100644 --- a/public/index.html +++ b/public/index.html @@ -71,7 +71,7 @@ button{touch-action:manipulation} #sunGlobalSearchBtn kbd{display:none!important} #sunSyncSettingsCard>div[style*="grid-template-columns"]{grid-template-columns:1fr!important} -}
Солнце Кейтеринг

Стоимость позиций0 ₽

Предоплата0 ₽

К оплате0 ₽

Заказы

МероприятиеДата и времяАдресСуммаСтатусДействия

Нажмите «Изменить», чтобы открыть заказ. Сумма, предоплата и остаток сохраняются вместе с заказом.

Склад

В этой версии каталог продуктов пополняется из составов боксов. Остатки и цены добавим следующим шагом.

Статистика

- + diff --git a/public/service-worker.js b/public/service-worker.js index 89d7c45..5963053 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,5 +1,5 @@ -const CACHE='sun-catering-pwa-v70-20260908-v17-6-5-stability'; -const VERSION='20260908-v17-6-5-stability'; +const CACHE='sun-catering-pwa-v71-20260908-v17-6-6-offer-templates'; +const VERSION='20260908-v17-6-6-offer-templates'; const CORE=[ './','./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}`, diff --git a/tests/app.spec.mjs b/tests/app.spec.mjs index e8ce463..f3d2b6f 100644 --- a/tests/app.spec.mjs +++ b/tests/app.spec.mjs @@ -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)} }); 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(()=>{ 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'))}; + 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.snapOne).toBe('midnight-glass'); expect(result.two).toBe('emerald-gold'); 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 }) => { 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})); expect(result.count).toBeLessThanOrEqual(2); 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.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 page.waitForTimeout(2600); expect(await page.evaluate(()=>window.__devChecks)).toBeLessThanOrEqual(2); -}); \ No newline at end of file +}); + +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); +}); diff --git a/tests/release-check.mjs b/tests/release-check.mjs index 5c8b07b..d681b1d 100644 --- a/tests/release-check.mjs +++ b/tests/release-check.mjs @@ -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(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(sw.includes('v17-6-5-stability')&&sw.includes('ux-fixes-v1764.js'),'service worker cache is v17.6.5'); -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(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-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("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'); @@ -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('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('fs.existsSync(path.join(root,`docs/releases/V${v}-CHANGES.txt`))),'release notes exist through v17.6.5'); +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','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("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('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(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); diff --git a/tests/static-security.mjs b/tests/static-security.mjs index 41837c8..6166778 100644 --- a/tests/static-security.mjs +++ b/tests/static-security.mjs @@ -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'); 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(!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(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(!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-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("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('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('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}`); @@ -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 ['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(!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(process.exitCode)process.exit(process.exitCode); \ No newline at end of file