diff --git a/public/core/ux-fixes-v1764.js b/public/core/ux-fixes-v1764.js
index 5b71ad3..cd44b41 100644
--- a/public/core/ux-fixes-v1764.js
+++ b/public/core/ux-fixes-v1764.js
@@ -176,7 +176,18 @@
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 offerPickerSignature(){
+ const list=templateList(),current=activeTemplateId();
+ return JSON.stringify({current,templates:list.map(t=>[String(t?.id||''),String(t?.name||'')])});
+ }
+ function renderOfferPicker({force=false}={}){
+ const box=$('sunOfferTemplateV1764');if(!box)return false;
+ const signature=offerPickerSignature();
+ if(!force&&box.dataset.sunV1764Signature===signature)return false;
+ box.innerHTML=pickerHtml();
+ box.dataset.sunV1764Signature=signature;
+ return true;
+ }
function setActiveOffer(id){if(id==null||id==='')return;activeOfferOrderId=String(id);setTimeout(ensureOfferPicker,0)}
function clearActiveOffer(){activeOfferOrderId=null}
function patchOfferOpenExports(){
diff --git a/tests/ui-stability.spec.mjs b/tests/ui-stability.spec.mjs
index 7a5dc84..42550c9 100644
--- a/tests/ui-stability.spec.mjs
+++ b/tests/ui-stability.spec.mjs
@@ -118,6 +118,46 @@ test('a delayed menu editor cannot move back into a section that was already lef
await expect(page.locator('#editor')).not.toHaveClass(/\bon\b/);
});
+
+test('offer template picker is not rebuilt by the five-second maintenance timer',async({page})=>{
+ await fixture(page,'
');
+ await page.evaluate(()=>{
+ localStorage.setItem('sunOrders',JSON.stringify([{id:'one',clientOfferTemplateId:'light'}]));
+ window.SunOfferTemplate={
+ get:()=>({id:'light',template:{id:'light',name:'Светлый'}}),
+ list:()=>[{id:'light',name:'Светлый'},{id:'editorial-grid',name:'Редакционный'}]
+ };
+ window.CateriumProposalPDF={resolveTemplate:id=>id,IDS:[]};
+ window.sunOpenClientOffer=()=>{};
+ window.sunOpenCurrentClientOffer=()=>{};
+ });
+ await page.addScriptTag({url:'/core/ux-fixes-v1764.js'});
+ await page.locator('#offerOpen').click();
+ const picker=page.locator('#sunOfferTemplateV1764');
+ await expect(picker).toBeVisible();
+ await expect(picker.locator('[data-v1764-offer-template]')).toHaveCount(2);
+ const before=await picker.evaluate(el=>({
+ first:el.querySelector('[data-v1764-offer-template]'),
+ html:el.innerHTML,
+ signature:el.dataset.sunV1764Signature
+ }));
+ await page.evaluate(()=>{
+ const picker=document.getElementById('sunOfferTemplateV1764');
+ window.offerPickerMutations=0;
+ new MutationObserver(records=>{window.offerPickerMutations+=records.filter(r=>r.type==='childList').length}).observe(picker,{childList:true,subtree:true});
+ });
+ await page.waitForTimeout(5300);
+ expect(await page.evaluate(()=>window.offerPickerMutations)).toBe(0);
+ expect(await picker.evaluate((el,before)=>el.innerHTML===before.html&&el.dataset.sunV1764Signature===before.signature,before)).toBe(true);
+ await picker.locator('[data-v1764-offer-template="editorial-grid"]').click();
+ await expect(picker.locator('[data-v1764-offer-template="editorial-grid"]')).toHaveClass(/\bon\b/);
+ const afterSelection=await page.evaluate(()=>window.offerPickerMutations);
+ expect(afterSelection).toBeGreaterThan(0);
+ await page.evaluate(()=>window.offerPickerMutations=0);
+ await page.waitForTimeout(5300);
+ expect(await page.evaluate(()=>window.offerPickerMutations)).toBe(0);
+});
+
test('full app stays stable across settings, menu, calendar and delayed background refreshes',async({page})=>{
const errors=[];page.on('pageerror',e=>errors.push(String(e)));
const workspaceId='22222222-2222-4222-8222-222222222222',servedRpcs=new Set();