Caterium v17.6.5 stability hardening
* Prepare v17.6.5 stability hardening patch * Move v17.6.5 stability patch logic to script * Fix v17.6.5 stability patch workflow * Harden Caterium v17.6.5 stability * Remove one-time v17.6.5 patch workflow * Remove one-time v17.6.5 patch script * Update static stability checks for v17.6.5 * Fix v17.6.5 E2E version expectation * Update v17.6.5 E2E version expectation * Remove one-time v17.6.5 test patch workflow * Capture failing v17.6.5 E2E diagnostics * Capture v17.6.5 E2E failures * Make v17.6.5 stability E2E deterministic * Make v17.6.5 E2E checks deterministic * Remove one-time deterministic E2E workflow * Remove one-time E2E diagnostic workflow * Remove temporary E2E diagnostics --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
parent
fc6d1aa778
commit
71d9cdc92e
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"app": "Caterium",
|
"app": "Caterium",
|
||||||
"version": "v17.6.4",
|
"version": "v17.6.5",
|
||||||
"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": "v69-20260907-v17-6-4-settings-orders-pdf-menu",
|
"pwaCache": "v70-20260908-v17-6-5-stability",
|
||||||
"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": "Production v17.6.4: chat photo compression and server memory counters; bounded Developer Console memory refresh; operations UX; Developer/SaaS hotfixes; one-minute order auto-completion and payment; per-offer PDF templates; styled Menu icon; GitHub QA and Cloudflare production deployment.",
|
"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.",
|
||||||
"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": "20260907-v17-6-4-settings-orders-pdf-menu",
|
"release": "20260908-v17-6-5-stability",
|
||||||
"registrationFlow": "email-password-confirm-company-auto-login",
|
"registrationFlow": "email-password-confirm-company-auto-login",
|
||||||
"emailConfirmationRequired": false,
|
"emailConfirmationRequired": false,
|
||||||
"employeeInviteLinks": false,
|
"employeeInviteLinks": false,
|
||||||
@ -313,5 +313,16 @@
|
|||||||
"ordersAutoCompletedOriginalDatePreserved": true,
|
"ordersAutoCompletedOriginalDatePreserved": true,
|
||||||
"clientOfferTemplatePerOrder": true,
|
"clientOfferTemplatePerOrder": true,
|
||||||
"menuStyledSvgIcon": true,
|
"menuStyledSvgIcon": true,
|
||||||
"releaseIntegrityChecks": true
|
"releaseIntegrityChecks": true,
|
||||||
|
"cloudRpcTimeoutMs": 12000,
|
||||||
|
"cloudConflictMaxRetries": 4,
|
||||||
|
"errorLogDedupMinutes": 5,
|
||||||
|
"networkFailureBackoff": true,
|
||||||
|
"stabilityLoggerVersion": "17.6.5",
|
||||||
|
"developerAdminCheckTtlMs": 30000,
|
||||||
|
"supportRefreshLightweight": true,
|
||||||
|
"supportRefreshIntervalMs": 20000,
|
||||||
|
"indexCacheBustCurrent": true,
|
||||||
|
"backgroundImageMutationBatching": true,
|
||||||
|
"opsBootWaitBounded": true
|
||||||
}
|
}
|
||||||
|
|||||||
12
docs/releases/V17.6.5-CHANGES.txt
Normal file
12
docs/releases/V17.6.5-CHANGES.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
Caterium v17.6.5 — Stability hardening
|
||||||
|
Date: 2026-09-08
|
||||||
|
|
||||||
|
1. Fixed stale browser asset versioning: index.html now loads runtime/core assets with the current release token instead of v17.6.0.
|
||||||
|
2. Supabase application-state RPCs have a 12-second UI timeout; sync cannot remain stuck forever in “Синхронизация…”.
|
||||||
|
3. SUN_CONFLICT retries use exponential backoff and stop after four safe retries.
|
||||||
|
4. Stability logging reports the real app version, deduplicates repeated identical errors for five minutes, prevents concurrent mirror/backup/error flush runs and backs off on network failures.
|
||||||
|
5. Developer-admin checks are cached/throttled and protected by an 8-second timeout.
|
||||||
|
6. Read-only support live refresh updates the company snapshot in place instead of clearing and re-entering the workspace every cycle.
|
||||||
|
7. Operations boot polling is bounded, background maintenance is slower/cleanable, and image mutation scanning is batched.
|
||||||
|
8. Added regression checks for idle timer errors, lightweight support refresh, Developer polling, cache version consistency and cloud stability guards.
|
||||||
|
9. PWA cache: v70 / 20260908-v17-6-5-stability.
|
||||||
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "caterium-app",
|
"name": "caterium-app",
|
||||||
"version": "17.6.4",
|
"version": "17.6.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "caterium-app",
|
"name": "caterium-app",
|
||||||
"version": "17.6.4",
|
"version": "17.6.5",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.51.0",
|
"@playwright/test": "^1.51.0",
|
||||||
"http-server": "^14.1.1",
|
"http-server": "^14.1.1",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "caterium-app",
|
"name": "caterium-app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "17.6.4",
|
"version": "17.6.5",
|
||||||
"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",
|
||||||
|
|||||||
@ -1993,6 +1993,13 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
let syncTimer = null;
|
let syncTimer = null;
|
||||||
let isSyncing = false;
|
let isSyncing = false;
|
||||||
let dirty = false;
|
let dirty = false;
|
||||||
|
const CLOUD_RPC_TIMEOUT_MS=12000;
|
||||||
|
const CLOUD_CONFLICT_MAX_RETRIES=4;
|
||||||
|
async function sunCloudAwait(promise,label='Облачный запрос',timeout=CLOUD_RPC_TIMEOUT_MS){
|
||||||
|
let timer=0;
|
||||||
|
try{return await Promise.race([Promise.resolve(promise),new Promise((_,reject)=>{timer=setTimeout(()=>reject(new Error(label+': превышено время ожидания ('+Math.round(timeout/1000)+' сек.)')),timeout)})]);}
|
||||||
|
finally{if(timer)clearTimeout(timer)}
|
||||||
|
}
|
||||||
let suppressStorageTracking = false;
|
let suppressStorageTracking = false;
|
||||||
let observedSignature = '';
|
let observedSignature = '';
|
||||||
let lastStatus = 'local';
|
let lastStatus = 'local';
|
||||||
@ -2628,14 +2635,14 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
|
|
||||||
async function fetchRemoteRow() {
|
async function fetchRemoteRow() {
|
||||||
if (!client || !workspace?.id) return null;
|
if (!client || !workspace?.id) return null;
|
||||||
const {data,error} = await client.rpc('sun_fetch_app_state',{p_workspace:workspace.id});
|
const {data,error} = await sunCloudAwait(client.rpc('sun_fetch_app_state',{p_workspace:workspace.id}),'Загрузка облачной базы');
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return Array.isArray(data) ? (data[0] || null) : (data || null);
|
return Array.isArray(data) ? (data[0] || null) : (data || null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function upsertPayload(payload, expectedRevision=null) {
|
async function upsertPayload(payload, expectedRevision=null) {
|
||||||
const args={p_workspace:workspace.id,p_payload:payload,p_client_id:clientId(),p_expected_revision:expectedRevision==null?null:Number(expectedRevision)};
|
const args={p_workspace:workspace.id,p_payload:payload,p_client_id:clientId(),p_expected_revision:expectedRevision==null?null:Number(expectedRevision)};
|
||||||
const {data,error} = await client.rpc('sun_save_app_state_v17',args);
|
const {data,error} = await sunCloudAwait(client.rpc('sun_save_app_state_v17',args),'Сохранение облачной базы');
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return Array.isArray(data) ? data[0] : data;
|
return Array.isArray(data) ? data[0] : data;
|
||||||
}
|
}
|
||||||
@ -2692,7 +2699,7 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
} catch (error) { handleError(error,'Не удалось загрузить облачные данные.'); }
|
} catch (error) { handleError(error,'Не удалось загрузить облачные данные.'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function syncNow({quiet=false}={}) {
|
async function syncNow({quiet=false,retryCount=0}={}) {
|
||||||
if (isSyncing || !client || !session || !workspace?.id || !workspaceMigrated()) return;
|
if (isSyncing || !client || !session || !workspace?.id || !workspaceMigrated()) return;
|
||||||
if (!navigator.onLine) { setStatus('offline','Офлайн — изменения ждут подключения.'); return; }
|
if (!navigator.onLine) { setStatus('offline','Офлайн — изменения ждут подключения.'); return; }
|
||||||
isSyncing = true;
|
isSyncing = true;
|
||||||
@ -2741,9 +2748,10 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
const msg=String(error?.message||'');
|
const msg=String(error?.message||'');
|
||||||
if(msg.includes('SUN_CONFLICT')){
|
if(msg.includes('SUN_CONFLICT')){
|
||||||
setStatus('pending','Облачная версия изменилась. Повторяю безопасную синхронизацию…');
|
const nextRetry=Number(retryCount||0)+1;
|
||||||
try{window.SunStabilityV17?.recordConflict?.(msg);}catch(_){}
|
try{window.SunStabilityV17?.recordConflict?.(msg);}catch(_){}
|
||||||
setTimeout(()=>syncNow({quiet:true}),350);
|
if(nextRetry>CLOUD_CONFLICT_MAX_RETRIES){handleError(error,'Не удалось синхронизировать после нескольких безопасных повторов.');}
|
||||||
|
else{const delay=Math.min(5000,400*Math.pow(2,nextRetry-1));setStatus('pending','Облачная версия изменилась. Повтор '+nextRetry+'/'+CLOUD_CONFLICT_MAX_RETRIES+' через '+(Math.round(delay/100)/10)+' сек.…');setTimeout(()=>syncNow({quiet:true,retryCount:nextRetry}),delay);}
|
||||||
}else handleError(error,'Ошибка синхронизации.');
|
}else handleError(error,'Ошибка синхронизации.');
|
||||||
}
|
}
|
||||||
finally { isSyncing=false; }
|
finally { isSyncing=false; }
|
||||||
@ -3088,12 +3096,12 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
async function enterSupportWorkspace(workspaceId,name='Компания') {
|
async function enterSupportWorkspace(workspaceId,name='Компания') {
|
||||||
const id=String(workspaceId||'').trim();if(!id)throw new Error('Не выбрана компания.');
|
const id=String(workspaceId||'').trim();if(!id)throw new Error('Не выбрана компания.');
|
||||||
if(!client||!session?.user)throw new Error('Сначала войдите в аккаунт разработчика.');
|
if(!client||!session?.user)throw new Error('Сначала войдите в аккаунт разработчика.');
|
||||||
const admin=await client.rpc('sun_is_platform_admin');if(admin.error)throw admin.error;if(admin.data!==true)throw new Error('Требуется аккаунт разработчика.');
|
const admin=await sunCloudAwait(client.rpc('sun_is_platform_admin'),'Проверка доступа разработчика',8000);if(admin.error)throw admin.error;if(admin.data!==true)throw new Error('Требуется аккаунт разработчика.');
|
||||||
const previousWorkspaceId=String(config.workspaceId||'');
|
const previousWorkspaceId=String(config.workspaceId||'');
|
||||||
const previousLocalWorkspaceId=String(config.localWorkspaceId||workspace?.id||'');
|
const previousLocalWorkspaceId=String(config.localWorkspaceId||workspace?.id||'');
|
||||||
if(previousLocalWorkspaceId)await captureTenantLocal(previousLocalWorkspaceId);
|
if(previousLocalWorkspaceId)await captureTenantLocal(previousLocalWorkspaceId);
|
||||||
clearTenantLocal();config.localWorkspaceId='';saveConfig();unsubscribeRealtime();
|
clearTenantLocal();config.localWorkspaceId='';saveConfig();unsubscribeRealtime();
|
||||||
const result=await client.rpc('sun_dev_support_snapshot',{p_workspace:id});if(result.error)throw result.error;
|
const result=await sunCloudAwait(client.rpc('sun_dev_support_snapshot',{p_workspace:id}),'Загрузка компании в режиме поддержки',12000);if(result.error)throw result.error;
|
||||||
const row=Array.isArray(result.data)?result.data[0]:result.data;if(!row?.payload)throw new Error('У компании нет облачной рабочей копии.');
|
const row=Array.isArray(result.data)?result.data[0]:result.data;if(!row?.payload)throw new Error('У компании нет облачной рабочей копии.');
|
||||||
await applyPayload(row.payload,true);
|
await applyPayload(row.payload,true);
|
||||||
supportMode={workspaceId:id,name:String(name||'Компания'),previousWorkspaceId,previousLocalWorkspaceId,enteredAt:new Date().toISOString()};
|
supportMode={workspaceId:id,name:String(name||'Компания'),previousWorkspaceId,previousLocalWorkspaceId,enteredAt:new Date().toISOString()};
|
||||||
@ -3103,6 +3111,17 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
renderCloudUI();return clone(supportMode);
|
renderCloudUI();return clone(supportMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function refreshSupportWorkspace(){
|
||||||
|
if(!supportMode||!client||!session?.user)return false;
|
||||||
|
const id=String(supportMode.workspaceId||workspace?.id||'').trim();if(!id)return false;
|
||||||
|
const result=await sunCloudAwait(client.rpc('sun_dev_support_snapshot',{p_workspace:id}),'Обновление данных компании',12000);if(result.error)throw result.error;
|
||||||
|
const row=Array.isArray(result.data)?result.data[0]:result.data;if(!row?.payload)throw new Error('У компании нет облачной рабочей копии.');
|
||||||
|
await applyPayload(row.payload,true);dirty=false;config.lastSync=new Date().toISOString();saveConfig();
|
||||||
|
setStatus('ready','Режим поддержки · '+(supportMode.name||'Компания')+' · только просмотр');
|
||||||
|
try{window.dispatchEvent(new CustomEvent('sun:cloud-state-applied',{detail:{workspaceId:id,support:true,refresh:true}}));}catch(_){}
|
||||||
|
renderCloudUI();return true;
|
||||||
|
}
|
||||||
|
|
||||||
async function exitSupportWorkspace() {
|
async function exitSupportWorkspace() {
|
||||||
if(!supportMode)return false;
|
if(!supportMode)return false;
|
||||||
clearTenantLocal();supportMode=null;workspace=null;config.localWorkspaceId='';saveConfig();
|
clearTenantLocal();supportMode=null;workspace=null;config.localWorkspaceId='';saveConfig();
|
||||||
@ -3137,6 +3156,7 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
getWorkspace:()=>workspace?clone(workspace):null,
|
getWorkspace:()=>workspace?clone(workspace):null,
|
||||||
getClientId:()=>clientId(),
|
getClientId:()=>clientId(),
|
||||||
enterSupportWorkspace,
|
enterSupportWorkspace,
|
||||||
|
refreshSupportWorkspace,
|
||||||
exitSupportWorkspace,
|
exitSupportWorkspace,
|
||||||
isSupportMode:()=>Boolean(supportMode),
|
isSupportMode:()=>Boolean(supportMode),
|
||||||
getSupportMode:()=>supportMode?clone(supportMode):null,
|
getSupportMode:()=>supportMode?clone(supportMode):null,
|
||||||
@ -4177,12 +4197,22 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
'use strict';
|
'use strict';
|
||||||
if (window.SunStabilityV17) return;
|
if (window.SunStabilityV17) return;
|
||||||
|
|
||||||
const VERSION = '17.0.0-local-test';
|
const VERSION = '17.6.5';
|
||||||
const RELEASE = 'v17 Stability · Local Test';
|
const RELEASE = 'v17.6.5 Stability';
|
||||||
const DB_NAME = 'SunStabilityV17';
|
const DB_NAME = 'SunStabilityV17';
|
||||||
const DB_VERSION = 1;
|
const DB_VERSION = 1;
|
||||||
const DAILY_KEY = 'sunV17DailyBackup';
|
const DAILY_KEY = 'sunV17DailyBackup';
|
||||||
|
const ERROR_DEDUPE_MS=5*60*1000;
|
||||||
|
const STABILITY_RPC_TIMEOUT_MS=10000;
|
||||||
|
const NETWORK_BACKOFF_MAX_MS=5*60*1000;
|
||||||
|
const errorSeen=new Map();
|
||||||
|
let flushBusy=false,mirrorBusy=false,backupBusy=false,initialBusy=false,networkFailures=0,nextNetworkAttemptAt=0;
|
||||||
const $ = id => document.getElementById(id);
|
const $ = id => document.getElementById(id);
|
||||||
|
const networkish=e=>/failed to fetch|networkerror|load failed|network|timeout|время ожидания/i.test(String(e?.message||e||''));
|
||||||
|
function noteNetworkFailure(){networkFailures=Math.min(networkFailures+1,8);nextNetworkAttemptAt=Date.now()+Math.min(NETWORK_BACKOFF_MAX_MS,5000*Math.pow(2,networkFailures-1));}
|
||||||
|
function noteNetworkSuccess(){networkFailures=0;nextNetworkAttemptAt=0;}
|
||||||
|
function networkReady(){return navigator.onLine&&Date.now()>=nextNetworkAttemptAt;}
|
||||||
|
async function stabilityRpc(promise,label){let timer=0;try{return await Promise.race([Promise.resolve(promise),new Promise((_,reject)=>{timer=setTimeout(()=>reject(new Error(label+': timeout')),STABILITY_RPC_TIMEOUT_MS)})]);}finally{if(timer)clearTimeout(timer)}}
|
||||||
const esc=window.SunSafe.escapeHTML;
|
const esc=window.SunSafe.escapeHTML;
|
||||||
const toast=(t,k='info',ms=4500)=>{try{return window.SunEnterprise?.toast?.(t,k,ms)}catch(_){} console.log('[V17]',t)};
|
const toast=(t,k='info',ms=4500)=>{try{return window.SunEnterprise?.toast?.(t,k,ms)}catch(_){} console.log('[V17]',t)};
|
||||||
const cloud=()=>window.SunCloudV2;
|
const cloud=()=>window.SunCloudV2;
|
||||||
@ -4232,39 +4262,33 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
return {url:location.href,online:navigator.onLine,userAgent:navigator.userAgent,release:RELEASE,cloudStatus:cloud()?.status?.()||null};
|
return {url:location.href,online:navigator.onLine,userAgent:navigator.userAgent,release:RELEASE,cloudStatus:cloud()?.status?.()||null};
|
||||||
}
|
}
|
||||||
async function queueError(message,stack='',level='error',extra={}){
|
async function queueError(message,stack='',level='error',extra={}){
|
||||||
const item={id:crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random()}`,workspaceId:workspace()?.id||null,clientId:clientId(),version:VERSION,level,message:String(message||'Unknown error').slice(0,4000),stack:String(stack||'').slice(0,12000),context:{...context(),...extra},createdAt:Date.now()};
|
const text=String(message||'Unknown error').slice(0,4000),wid=workspace()?.id||null,key=level+'|'+(wid||'')+'|'+text,now=Date.now();
|
||||||
await idbPut('errors',item);flushErrors();refreshCardSoon();
|
const seen=errorSeen.get(key)||0;if(now-seen<ERROR_DEDUPE_MS)return false;errorSeen.set(key,now);
|
||||||
|
const item={id:crypto.randomUUID?crypto.randomUUID():Date.now()+'-'+Math.random(),workspaceId:wid,clientId:clientId(),version:VERSION,level,message:text,stack:String(stack||'').slice(0,12000),context:{...context(),...extra},createdAt:now};
|
||||||
|
await idbPut('errors',item);if(networkReady())flushErrors();refreshCardSoon();return true;
|
||||||
}
|
}
|
||||||
async function flushErrors(){
|
async function flushErrors(){
|
||||||
const c=client(),ws=workspace();if(!c||!navigator.onLine)return;
|
const c=client(),ws=workspace();if(flushBusy||!c||!networkReady())return;flushBusy=true;
|
||||||
const items=await idbAll('errors');
|
try{const items=await idbAll('errors');for(const item of items.slice(0,20)){try{const {error}=await stabilityRpc(c.rpc('sun_v17_log_error',{p_workspace:item.workspaceId||ws?.id||null,p_client_id:item.clientId,p_app_version:item.version,p_level:item.level,p_message:item.message,p_stack:item.stack,p_context:item.context}),'Журнал ошибок');if(error)throw error;await idbDelete('errors',item.id);noteNetworkSuccess();}catch(e){if(networkish(e))noteNetworkFailure();break;}}}
|
||||||
for(const item of items.slice(0,30)){
|
finally{flushBusy=false}
|
||||||
try{
|
|
||||||
const {error}=await c.rpc('sun_v17_log_error',{p_workspace:item.workspaceId||ws?.id||null,p_client_id:item.clientId,p_app_version:item.version,p_level:item.level,p_message:item.message,p_stack:item.stack,p_context:item.context});
|
|
||||||
if(!error)await idbDelete('errors',item.id);
|
|
||||||
}catch(_){}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mirror(payload){
|
async function mirror(payload){
|
||||||
const c=client(),ws=workspace();if(!c||!ws?.id||!payload||!navigator.onLine)return null;
|
const c=client(),ws=workspace();if(mirrorBusy||!c||!ws?.id||!payload||!networkReady())return null;mirrorBusy=true;
|
||||||
try{
|
try{const {data,error}=await stabilityRpc(c.rpc('sun_v17_mirror_legacy',{p_workspace:ws.id,p_payload:payload,p_client_id:clientId()}),'Зеркалирование v17');if(error)throw error;noteNetworkSuccess();lastMirrorAt=new Date().toISOString();await idbPut('meta',{key:'mirror:'+ws.id,at:Date.now(),result:data||{}});return data;}
|
||||||
const {data,error}=await c.rpc('sun_v17_mirror_legacy',{p_workspace:ws.id,p_payload:payload,p_client_id:clientId()});
|
catch(e){if(networkish(e))noteNetworkFailure();await queueError('Ошибка зеркалирования v17: '+(e.message||e),e.stack||'','warn');return null}
|
||||||
if(error)throw error;lastMirrorAt=new Date().toISOString();await idbPut('meta',{key:`mirror:${ws.id}`,at:Date.now(),result:data||{}});return data;
|
finally{mirrorBusy=false}
|
||||||
}catch(e){await queueError(`Ошибка зеркалирования v17: ${e.message||e}`,e.stack||'','warn');return null}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureDailyBackup(){
|
async function ensureDailyBackup(){
|
||||||
const c=client(),ws=workspace();if(!c||!ws?.id||!navigator.onLine)return;
|
const c=client(),ws=workspace();if(backupBusy||!c||!ws?.id||!networkReady())return;backupBusy=true;
|
||||||
const today=new Date().toISOString().slice(0,10);let state={};try{state=JSON.parse(localStorage.getItem(DAILY_KEY)||'{}')||{}}catch(_){}
|
try{const today=new Date().toISOString().slice(0,10);let state={};try{state=JSON.parse(localStorage.getItem(DAILY_KEY)||'{}')||{}}catch(_){}if(state[ws.id]===today)return;const {error}=await stabilityRpc(c.rpc('sun_v17_create_backup',{p_workspace:ws.id,p_kind:'daily',p_label:'Автоматическая копия '+today}),'Ежедневная резервная копия');if(error)throw error;await stabilityRpc(c.rpc('sun_v17_prune_backups',{p_workspace:ws.id}),'Очистка старых резервных копий');noteNetworkSuccess();state[ws.id]=today;localStorage.setItem(DAILY_KEY,JSON.stringify(state));}
|
||||||
if(state[ws.id]===today)return;
|
catch(e){if(networkish(e))noteNetworkFailure();await queueError('Не удалось создать ежедневную резервную копию: '+(e.message||e),e.stack||'','warn')}
|
||||||
try{
|
finally{backupBusy=false}
|
||||||
const {error}=await c.rpc('sun_v17_create_backup',{p_workspace:ws.id,p_kind:'daily',p_label:`Автоматическая копия ${today}`});if(error)throw error;
|
|
||||||
await c.rpc('sun_v17_prune_backups',{p_workspace:ws.id});state[ws.id]=today;localStorage.setItem(DAILY_KEY,JSON.stringify(state));
|
|
||||||
}catch(e){await queueError(`Не удалось создать ежедневную резервную копию: ${e.message||e}`,e.stack||'','warn')}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onCloudSync(ev){
|
async function onCloudSync(ev){
|
||||||
|
noteNetworkSuccess();
|
||||||
const payload=ev?.detail?.payload;
|
const payload=ev?.detail?.payload;
|
||||||
if(payload)await mirror(payload);
|
if(payload)await mirror(payload);
|
||||||
await ensureDailyBackup();await flushErrors();refreshCardSoon();
|
await ensureDailyBackup();await flushErrors();refreshCardSoon();
|
||||||
@ -4319,11 +4343,12 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function initialMirrorIfPossible(){
|
async function initialMirrorIfPossible(){
|
||||||
const c=client(),ws=workspace();if(!c||!ws?.id||!navigator.onLine)return;
|
const c=client(),ws=workspace();if(initialBusy||!c||!ws?.id||!networkReady())return;initialBusy=true;
|
||||||
try{const payload=await cloud()?.snapshotLocal?.();if(payload)await mirror(payload);await ensureDailyBackup();await flushErrors()}catch(e){queueError(e.message||String(e),e.stack||'','warn')}
|
try{const payload=await cloud()?.snapshotLocal?.();if(payload)await mirror(payload);await ensureDailyBackup();await flushErrors()}catch(e){await queueError(e.message||String(e),e.stack||'','warn')}
|
||||||
|
finally{initialBusy=false}
|
||||||
}
|
}
|
||||||
|
|
||||||
function boot(){if(booted)return;booted=true;installStyle();installErrorHooks();observeSettings();window.addEventListener('sun:cloud-sync-complete',onCloudSync);window.addEventListener('online',()=>{flushErrors();setTimeout(initialMirrorIfPossible,700)});window.addEventListener('sun:cloud-permissions-changed',()=>setTimeout(initialMirrorIfPossible,600));setTimeout(initialMirrorIfPossible,2200);setInterval(()=>{if(navigator.onLine)flushErrors()},60000)}
|
function boot(){if(booted)return;booted=true;installStyle();installErrorHooks();observeSettings();window.addEventListener('sun:cloud-sync-complete',onCloudSync);window.addEventListener('online',()=>{noteNetworkSuccess();flushErrors();setTimeout(initialMirrorIfPossible,900)});window.addEventListener('sun:cloud-permissions-changed',()=>setTimeout(initialMirrorIfPossible,900));setTimeout(initialMirrorIfPossible,2600);setInterval(()=>{if(networkReady())flushErrors()},60000)}
|
||||||
|
|
||||||
window.SunStabilityV17={VERSION,RELEASE,markPending,markSynced,recordConflict,queueError,renderCard,diagnostics,createBackup:createManualBackup,emergencyExport};
|
window.SunStabilityV17={VERSION,RELEASE,markPending,markSynced,recordConflict,queueError,renderCard,diagnostics,createBackup:createManualBackup,emergencyExport};
|
||||||
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
|
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
|
||||||
@ -5142,7 +5167,8 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
const TABS=[['overview','Обзор'],['companies','Компании'],['accounts','Аккаунты'],['plans','Тарифы'],['features','Функции'],['system','Система'],['backups','Резервные копии'],['journal','Журнал']];
|
const TABS=[['overview','Обзор'],['companies','Компании'],['accounts','Аккаунты'],['plans','Тарифы'],['features','Функции'],['system','Система'],['backups','Резервные копии'],['journal','Журнал']];
|
||||||
const FEATURES={orders:'Заказы',calendar:'Календарь',map:'Карта',clients:'Клиенты',catalog_view:'Каталог · просмотр',catalog_edit:'Каталог · редактирование',production:'Производство',shopping:'Закупки',stock:'Склад',routes:'Маршруты',mailings:'Рассылки',money:'Финансы',stats_basic:'Статистика',stats_advanced:'Расширенная статистика',team:'Команда',suppliers:'Поставщики',print:'Печать',settings:'Настройки',branding:'Фирменное оформление',client_offers:'Предложение клиенту',offer_templates:'Шаблоны предложения',backups:'Резервные копии',audit:'Журнал',users_manage:'Пользователи и права'};
|
const FEATURES={orders:'Заказы',calendar:'Календарь',map:'Карта',clients:'Клиенты',catalog_view:'Каталог · просмотр',catalog_edit:'Каталог · редактирование',production:'Производство',shopping:'Закупки',stock:'Склад',routes:'Маршруты',mailings:'Рассылки',money:'Финансы',stats_basic:'Статистика',stats_advanced:'Расширенная статистика',team:'Команда',suppliers:'Поставщики',print:'Печать',settings:'Настройки',branding:'Фирменное оформление',client_offers:'Предложение клиенту',offer_templates:'Шаблоны предложения',backups:'Резервные копии',audit:'Журнал',users_manage:'Пользователи и права'};
|
||||||
const PLANS={basic:'Базовый',professional:'Профессиональный',full:'Полный'};
|
const PLANS={basic:'Базовый',professional:'Профессиональный',full:'Полный'};
|
||||||
let platformAdmin=false,checking=false,activeTab=localStorage.getItem(TAB_KEY)||'overview',lastCompanies=[];
|
let platformAdmin=false,checking=false,activeTab=localStorage.getItem(TAB_KEY)||'overview',lastCompanies=[],lastAdminCheckAt=0;
|
||||||
|
const DEV_ADMIN_TTL_MS=30000,DEV_ADMIN_TIMEOUT_MS=8000;
|
||||||
|
|
||||||
function installStyle(){if($('sunDeveloperV22Style'))return;const st=document.createElement('style');st.id='sunDeveloperV22Style';st.textContent=`
|
function installStyle(){if($('sunDeveloperV22Style'))return;const st=document.createElement('style');st.id='sunDeveloperV22Style';st.textContent=`
|
||||||
#sunDeveloperNavV22{font-weight:800}.sun-dev-view{max-width:1460px;margin:0 auto;padding:24px}.sun-dev-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:14px}.sun-dev-head h1{margin:0}.sun-dev-head-actions{display:flex;gap:8px;flex-wrap:wrap}.sun-dev-tabs{display:flex;gap:7px;overflow-x:auto;padding:4px 2px 12px;scrollbar-width:thin;border-bottom:1px solid #e7e8ec;margin-bottom:18px}.sun-dev-tabs button{white-space:nowrap;border:1px solid #d8dbe2;background:#fff;border-radius:999px;padding:9px 14px;font-weight:750;cursor:pointer}.sun-dev-tabs button.on{background:#171815;color:#fff;border-color:#171815}.sun-dev-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}.sun-dev-kpi,.sun-dev-card{background:#fff;border:1px solid #e2e4e9;border-radius:16px;padding:16px;box-shadow:0 8px 28px rgba(30,32,36,.04)}.sun-dev-kpi small{color:#6f7480;display:block;margin-bottom:7px}.sun-dev-kpi b{font-size:24px}.sun-dev-card h2,.sun-dev-card h3{margin:0 0 10px}.sun-dev-toolbar{display:flex;gap:8px;align-items:end;flex-wrap:wrap;margin-bottom:12px}.sun-dev-toolbar label{min-width:180px}.sun-dev-toolbar input,.sun-dev-toolbar select,.sun-dev-card input,.sun-dev-card select,.sun-dev-card textarea{width:100%;box-sizing:border-box;padding:9px 10px;border:1px solid #d6d9df;border-radius:9px;background:#fff}.sun-dev-table-wrap{overflow:auto;border:1px solid #e2e4e9;border-radius:14px;background:#fff}.sun-dev-table{width:100%;border-collapse:collapse;min-width:900px}.sun-dev-table th,.sun-dev-table td{text-align:left;padding:11px 12px;border-bottom:1px solid #eceef2;vertical-align:top;font-size:13px}.sun-dev-table th{background:#f7f8fa;position:sticky;top:0;z-index:1}.sun-dev-actions{display:flex;gap:6px;flex-wrap:wrap}.sun-dev-pill{display:inline-flex;padding:4px 8px;border-radius:999px;background:#f0f2f5;font-size:11px;font-weight:750;margin:2px}.sun-dev-pill.ok{background:#e8f7ef;color:#14653b}.sun-dev-pill.warn{background:#fff3d9;color:#7d5300}.sun-dev-pill.bad{background:#fde8e8;color:#9d2424}.sun-dev-muted{color:#777d88;font-size:12px}.sun-dev-section-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.sun-dev-modal{position:fixed;inset:0;background:rgba(20,22,26,.52);z-index:100000;display:flex;align-items:center;justify-content:center;padding:18px}.sun-dev-modal-card{background:#fff;border-radius:18px;max-width:620px;width:min(620px,100%);max-height:90vh;overflow:auto;padding:20px;box-shadow:0 24px 80px rgba(0,0,0,.28)}.sun-dev-modal-head{display:flex;justify-content:space-between;gap:12px;align-items:center}.sun-dev-modal-head h2{margin:0}.sun-dev-modal-close{border:0;background:#f1f2f4;border-radius:10px;width:36px;height:36px;font-size:22px;cursor:pointer}.sun-dev-mfa-qr{max-width:220px;width:100%;display:block;margin:12px auto;border:10px solid #fff}.sun-dev-support-banner{position:fixed;left:50%;transform:translateX(-50%);bottom:16px;z-index:99995;background:#171815;color:#fff;border-radius:14px;padding:10px 12px;box-shadow:0 12px 36px rgba(0,0,0,.28);display:flex;align-items:center;gap:10px;max-width:calc(100vw - 30px)}.sun-dev-support-banner b{white-space:nowrap}.sun-dev-support-banner button{background:#fff;color:#171815;border:0;border-radius:9px;padding:7px 10px;font-weight:800;cursor:pointer}.sun-dev-danger{border:1px solid #efc8c8!important;color:#9f2424!important;background:#fff!important}.sun-dev-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;background:#f5f6f8;border-radius:8px;padding:7px;overflow-wrap:anywhere}.sun-dev-empty{padding:26px;text-align:center;color:#747a84}.sun-dev-check{display:flex;align-items:center;gap:8px}.sun-dev-check input{width:18px!important;height:18px}.sun-dev-pre{white-space:pre-wrap;word-break:break-word;font:12px/1.45 ui-monospace,SFMono-Regular,Consolas,monospace;background:#f7f8fa;padding:12px;border-radius:10px;max-height:340px;overflow:auto}
|
#sunDeveloperNavV22{font-weight:800}.sun-dev-view{max-width:1460px;margin:0 auto;padding:24px}.sun-dev-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:14px}.sun-dev-head h1{margin:0}.sun-dev-head-actions{display:flex;gap:8px;flex-wrap:wrap}.sun-dev-tabs{display:flex;gap:7px;overflow-x:auto;padding:4px 2px 12px;scrollbar-width:thin;border-bottom:1px solid #e7e8ec;margin-bottom:18px}.sun-dev-tabs button{white-space:nowrap;border:1px solid #d8dbe2;background:#fff;border-radius:999px;padding:9px 14px;font-weight:750;cursor:pointer}.sun-dev-tabs button.on{background:#171815;color:#fff;border-color:#171815}.sun-dev-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}.sun-dev-kpi,.sun-dev-card{background:#fff;border:1px solid #e2e4e9;border-radius:16px;padding:16px;box-shadow:0 8px 28px rgba(30,32,36,.04)}.sun-dev-kpi small{color:#6f7480;display:block;margin-bottom:7px}.sun-dev-kpi b{font-size:24px}.sun-dev-card h2,.sun-dev-card h3{margin:0 0 10px}.sun-dev-toolbar{display:flex;gap:8px;align-items:end;flex-wrap:wrap;margin-bottom:12px}.sun-dev-toolbar label{min-width:180px}.sun-dev-toolbar input,.sun-dev-toolbar select,.sun-dev-card input,.sun-dev-card select,.sun-dev-card textarea{width:100%;box-sizing:border-box;padding:9px 10px;border:1px solid #d6d9df;border-radius:9px;background:#fff}.sun-dev-table-wrap{overflow:auto;border:1px solid #e2e4e9;border-radius:14px;background:#fff}.sun-dev-table{width:100%;border-collapse:collapse;min-width:900px}.sun-dev-table th,.sun-dev-table td{text-align:left;padding:11px 12px;border-bottom:1px solid #eceef2;vertical-align:top;font-size:13px}.sun-dev-table th{background:#f7f8fa;position:sticky;top:0;z-index:1}.sun-dev-actions{display:flex;gap:6px;flex-wrap:wrap}.sun-dev-pill{display:inline-flex;padding:4px 8px;border-radius:999px;background:#f0f2f5;font-size:11px;font-weight:750;margin:2px}.sun-dev-pill.ok{background:#e8f7ef;color:#14653b}.sun-dev-pill.warn{background:#fff3d9;color:#7d5300}.sun-dev-pill.bad{background:#fde8e8;color:#9d2424}.sun-dev-muted{color:#777d88;font-size:12px}.sun-dev-section-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.sun-dev-modal{position:fixed;inset:0;background:rgba(20,22,26,.52);z-index:100000;display:flex;align-items:center;justify-content:center;padding:18px}.sun-dev-modal-card{background:#fff;border-radius:18px;max-width:620px;width:min(620px,100%);max-height:90vh;overflow:auto;padding:20px;box-shadow:0 24px 80px rgba(0,0,0,.28)}.sun-dev-modal-head{display:flex;justify-content:space-between;gap:12px;align-items:center}.sun-dev-modal-head h2{margin:0}.sun-dev-modal-close{border:0;background:#f1f2f4;border-radius:10px;width:36px;height:36px;font-size:22px;cursor:pointer}.sun-dev-mfa-qr{max-width:220px;width:100%;display:block;margin:12px auto;border:10px solid #fff}.sun-dev-support-banner{position:fixed;left:50%;transform:translateX(-50%);bottom:16px;z-index:99995;background:#171815;color:#fff;border-radius:14px;padding:10px 12px;box-shadow:0 12px 36px rgba(0,0,0,.28);display:flex;align-items:center;gap:10px;max-width:calc(100vw - 30px)}.sun-dev-support-banner b{white-space:nowrap}.sun-dev-support-banner button{background:#fff;color:#171815;border:0;border-radius:9px;padding:7px 10px;font-weight:800;cursor:pointer}.sun-dev-danger{border:1px solid #efc8c8!important;color:#9f2424!important;background:#fff!important}.sun-dev-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;background:#f5f6f8;border-radius:8px;padding:7px;overflow-wrap:anywhere}.sun-dev-empty{padding:26px;text-align:center;color:#747a84}.sun-dev-check{display:flex;align-items:center;gap:8px}.sun-dev-check input{width:18px!important;height:18px}.sun-dev-pre{white-space:pre-wrap;word-break:break-word;font:12px/1.45 ui-monospace,SFMono-Regular,Consolas,monospace;background:#f7f8fa;padding:12px;border-radius:10px;max-height:340px;overflow:auto}
|
||||||
@ -5153,7 +5179,12 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
async function rpc(name,args={}){const c=client();if(!c)throw new Error('Supabase не подключён.');const r=await c.rpc(name,args);if(r.error)throw r.error;return r.data}
|
async function rpc(name,args={}){const c=client();if(!c)throw new Error('Supabase не подключён.');const r=await c.rpc(name,args);if(r.error)throw r.error;return r.data}
|
||||||
async function log(action,workspace=null,user=null,details={}){try{await rpc('sun_dev_log_event',{p_action:action,p_workspace:workspace,p_user:user,p_details:details})}catch(_){}}
|
async function log(action,workspace=null,user=null,details={}){try{await rpc('sun_dev_log_event',{p_action:action,p_workspace:workspace,p_user:user,p_details:details})}catch(_){}}
|
||||||
|
|
||||||
async function checkPlatformAdmin(force=false){if(checking)return platformAdmin;if(platformAdmin&&!force)return true;const c=client(),ss=session();if(!c||!ss?.user){platformAdmin=false;syncChrome();return false}checking=true;try{const r=await c.rpc('sun_is_platform_admin');platformAdmin=!r.error&&r.data===true}catch(_){platformAdmin=false}finally{checking=false;syncChrome()}return platformAdmin}
|
async function checkPlatformAdmin(force=false){
|
||||||
|
if(checking)return platformAdmin;const now=Date.now();if(!force&&lastAdminCheckAt&&now-lastAdminCheckAt<DEV_ADMIN_TTL_MS)return platformAdmin;
|
||||||
|
const c=client(),ss=session();if(!c||!ss?.user){platformAdmin=false;lastAdminCheckAt=now;syncChrome();return false}checking=true;let timer=0;
|
||||||
|
try{const r=await Promise.race([Promise.resolve(c.rpc('sun_is_platform_admin')),new Promise((_,reject)=>{timer=setTimeout(()=>reject(new Error('Developer access timeout')),DEV_ADMIN_TIMEOUT_MS)})]);platformAdmin=!r.error&&r.data===true;}
|
||||||
|
catch(_){platformAdmin=false}finally{clearTimeout(timer);lastAdminCheckAt=Date.now();checking=false;syncChrome()}return platformAdmin
|
||||||
|
}
|
||||||
function isPlatformAdmin(){return platformAdmin===true}
|
function isPlatformAdmin(){return platformAdmin===true}
|
||||||
|
|
||||||
function ensureNav(){installStyle();const nav=document.querySelector('header nav');if(!nav||!platformAdmin)return null;$('sunSaaSAdminNavV16')?.remove();let b=$('sunDeveloperNavV22');if(!b){b=document.createElement('button');b.id='sunDeveloperNavV22';b.type='button';b.dataset.navLabel='Разработчик';b.textContent='Разработчик';nav.appendChild(b);b.addEventListener('click',()=>open(b))}b.hidden=false;b.style.display='';return b}
|
function ensureNav(){installStyle();const nav=document.querySelector('header nav');if(!nav||!platformAdmin)return null;$('sunSaaSAdminNavV16')?.remove();let b=$('sunDeveloperNavV22');if(!b){b=document.createElement('button');b.id='sunDeveloperNavV22';b.type='button';b.dataset.navLabel='Разработчик';b.textContent='Разработчик';nav.appendChild(b);b.addEventListener('click',()=>open(b))}b.hidden=false;b.style.display='';return b}
|
||||||
@ -5240,7 +5271,7 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
|
|
||||||
function enhanceDeveloperGate(){const gate=$('sunCloudAuthGateV3'),ss=session(),ws=cloud()?.getWorkspace?.(),st=cloud()?.status?.();if(!platformAdmin||!gate||!ss?.user||ws||st?.membershipsLoading||st?.membershipsLoaded===false)return;const card=gate.querySelector('.sun-cloud-auth-card');if(!card||card.dataset.devEnhanced==='1')return;card.dataset.devEnhanced='1';gate.dataset.saasEnhanced='1';card.innerHTML=`<div class="sun-cloud-auth-brand"><img src="caterium-login-logo.png" alt="Caterium"><div><h2>Аккаунт разработчика</h2><div class="hint">${esc(ss.user.email||'')}</div></div></div><p class="hint">Этот аккаунт управляет платформой и не обязан иметь собственную рабочую компанию.</p><div class="sun-cloud-auth-actions"><button class="primary" type="button" data-open-dev>Открыть кабинет разработчика</button><button class="outline" type="button" data-signout>Выйти</button></div>`;card.querySelector('[data-open-dev]').onclick=()=>open();card.querySelector('[data-signout]').onclick=()=>cloud()?.signOut?.()}
|
function enhanceDeveloperGate(){const gate=$('sunCloudAuthGateV3'),ss=session(),ws=cloud()?.getWorkspace?.(),st=cloud()?.status?.();if(!platformAdmin||!gate||!ss?.user||ws||st?.membershipsLoading||st?.membershipsLoaded===false)return;const card=gate.querySelector('.sun-cloud-auth-card');if(!card||card.dataset.devEnhanced==='1')return;card.dataset.devEnhanced='1';gate.dataset.saasEnhanced='1';card.innerHTML=`<div class="sun-cloud-auth-brand"><img src="caterium-login-logo.png" alt="Caterium"><div><h2>Аккаунт разработчика</h2><div class="hint">${esc(ss.user.email||'')}</div></div></div><p class="hint">Этот аккаунт управляет платформой и не обязан иметь собственную рабочую компанию.</p><div class="sun-cloud-auth-actions"><button class="primary" type="button" data-open-dev>Открыть кабинет разработчика</button><button class="outline" type="button" data-signout>Выйти</button></div>`;card.querySelector('[data-open-dev]').onclick=()=>open();card.querySelector('[data-signout]').onclick=()=>cloud()?.signOut?.()}
|
||||||
function syncChrome(){if(platformAdmin){ensureNav();enhanceDeveloperGate()}else{$('sunDeveloperNavV22')?.remove()}ensureSupportBanner()}
|
function syncChrome(){if(platformAdmin){ensureNav();enhanceDeveloperGate()}else{$('sunDeveloperNavV22')?.remove()}ensureSupportBanner()}
|
||||||
function boot(){installStyle();let tries=0;const timer=setInterval(async()=>{tries++;if(client()&&session()?.user){await checkPlatformAdmin(tries%6===0);enhanceDeveloperGate();ensureSupportBanner()}else if(!session()?.user){platformAdmin=false;syncChrome()}if(tries>240)clearInterval(timer)},1500);window.addEventListener('sun:cloud-permissions-changed',()=>setTimeout(()=>checkPlatformAdmin(true),80));window.addEventListener('sun:cloud-state-applied',()=>ensureSupportBanner());document.addEventListener('click',e=>{const b=e.target.closest('header nav button');if(!b)return;if(String(b.dataset.navLabel||b.textContent||'').trim()==='Настройки'&&platformAdmin)setTimeout(()=>{$('sunSaaSSettingsCardV16')?.querySelector('[data-saas-admin]')?.remove()},120)},true);setTimeout(()=>checkPlatformAdmin(true),900)}
|
function boot(){installStyle();let tries=0;const timer=setInterval(async()=>{tries++;if(document.hidden)return;if(client()&&session()?.user){await checkPlatformAdmin(false);enhanceDeveloperGate();ensureSupportBanner()}else if(!session()?.user){platformAdmin=false;syncChrome()}if(tries>40)clearInterval(timer)},3000);window.addEventListener('sun:cloud-permissions-changed',()=>setTimeout(()=>checkPlatformAdmin(true),120));window.addEventListener('sun:cloud-state-applied',()=>ensureSupportBanner());document.addEventListener('click',e=>{const b=e.target.closest('header nav button');if(!b)return;if(String(b.dataset.navLabel||b.textContent||'').trim()==='Настройки'&&platformAdmin)setTimeout(()=>{$('sunSaaSSettingsCardV16')?.querySelector('[data-saas-admin]')?.remove()},120)},true);setTimeout(()=>checkPlatformAdmin(true),900)}
|
||||||
|
|
||||||
window.SunDeveloperV22={VERSION,open,isPlatformAdmin,checkPlatformAdmin,ensureNav,activateTab,ensureSupportBanner};
|
window.SunDeveloperV22={VERSION,open,isPlatformAdmin,checkPlatformAdmin,ensureNav,activateTab,ensureSupportBanner};
|
||||||
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
|
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
const dev=window.SunDeveloperV22,cloud=window.SunCloudV2;
|
const dev=window.SunDeveloperV22,cloud=window.SunCloudV2;
|
||||||
if(!dev||!cloud?.getSession?.()?.user)return false;
|
if(!dev||!cloud?.getSession?.()?.user)return false;
|
||||||
if(dev.isPlatformAdmin?.()===true)return true;
|
if(dev.isPlatformAdmin?.()===true)return true;
|
||||||
try{return await dev.checkPlatformAdmin?.(true)===true}catch(_){return false}
|
try{return await dev.checkPlatformAdmin?.(false)===true}catch(_){return false}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function enhanceDeveloperGate(){
|
async function enhanceDeveloperGate(){
|
||||||
@ -65,7 +65,7 @@
|
|||||||
window.addEventListener('sun:cloud-permissions-changed',()=>setTimeout(maintain,0));
|
window.addEventListener('sun:cloud-permissions-changed',()=>setTimeout(maintain,0));
|
||||||
window.addEventListener('sun:cloud-state-applied',()=>setTimeout(maintain,0));
|
window.addEventListener('sun:cloud-state-applied',()=>setTimeout(maintain,0));
|
||||||
setTimeout(maintain,0);
|
setTimeout(maintain,0);
|
||||||
setInterval(()=>{if(!document.hidden)maintain();},1000);
|
setInterval(()=>{if(!document.hidden)maintain();},10000);
|
||||||
|
|
||||||
window.SunHotfixV1763={VERSION,patchDeveloperOpen,enhanceDeveloperGate};
|
window.SunHotfixV1763={VERSION,patchDeveloperOpen,enhanceDeveloperGate};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
const VERSION='17.6.2';
|
const VERSION='17.6.2';
|
||||||
const RELEASE='20260907-v17-6-2-ops-ux';
|
const RELEASE='20260907-v17-6-2-ops-ux';
|
||||||
const SUPPORT_POLL_MS=12000;
|
const SUPPORT_POLL_MS=20000;
|
||||||
const ROUTE_BASE_KEY='sunRouteBaseV1';
|
const ROUTE_BASE_KEY='sunRouteBaseV1';
|
||||||
const ROUTE_OVERRIDE_KEY='sunRouteStartOverridesV1';
|
const ROUTE_OVERRIDE_KEY='sunRouteStartOverridesV1';
|
||||||
const ROUTE_DEFAULT={address:'Ленинградская область, Ломоносовский район, деревня Телези, 20',lat:59.701058,lon:29.977269};
|
const ROUTE_DEFAULT={address:'Ленинградская область, Ломоносовский район, деревня Телези, 20',lat:59.701058,lon:29.977269};
|
||||||
@ -76,7 +76,7 @@
|
|||||||
const c=cloud(),sm=support();if(!c||!sm||supportBusy||!navigator.onLine||document.hidden)return false;
|
const c=cloud(),sm=support();if(!c||!sm||supportBusy||!navigator.onLine||document.hidden)return false;
|
||||||
supportBusy=true;renderSupportStatus();
|
supportBusy=true;renderSupportStatus();
|
||||||
try{
|
try{
|
||||||
await c.enterSupportWorkspace(sm.workspaceId,sm.name||'Компания');
|
if(typeof c.refreshSupportWorkspace==='function')await c.refreshSupportWorkspace();else await c.enterSupportWorkspace(sm.workspaceId,sm.name||'Компания');
|
||||||
supportLastAt=Date.now();patchSupportPermissions();
|
supportLastAt=Date.now();patchSupportPermissions();
|
||||||
try{window.dispatchEvent(new CustomEvent('sun:cloud-permissions-changed',{detail:{workspace:c.getWorkspace?.(),support:true,refresh:true}}));}catch(_){}
|
try{window.dispatchEvent(new CustomEvent('sun:cloud-permissions-changed',{detail:{workspace:c.getWorkspace?.(),support:true,refresh:true}}));}catch(_){}
|
||||||
for(const name of ['render','renderOrders','renderStats','renderClients','sunRefreshStock']){try{window[name]?.()}catch(_){}}
|
for(const name of ['render','renderOrders','renderStats','renderClients','sunRefreshStock']){try{window[name]?.()}catch(_){}}
|
||||||
@ -245,15 +245,17 @@
|
|||||||
calendarMore:true
|
calendarMore:true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
let maintenanceTimer=0,waitTimer=0,waitAttempts=0;
|
||||||
function boot(){
|
function boot(){
|
||||||
installStyles();patchSupportPermissions();installMenuPage();maintainSupport();syncMenuPermission();enhanceRoutePage();
|
installStyles();patchSupportPermissions();installMenuPage();maintainSupport();syncMenuPermission();enhanceRoutePage();
|
||||||
document.addEventListener('click',e=>{const more=e.target.closest('#calendar .cal-more');if(more){e.preventDefault();e.stopImmediatePropagation();showCalendarDay(more.closest('.cal-day'));return}interceptRouteActions(e)},true);
|
document.addEventListener('click',e=>{const more=e.target.closest('#calendar .cal-more');if(more){e.preventDefault();e.stopImmediatePropagation();showCalendarDay(more.closest('.cal-day'));return}interceptRouteActions(e)},true);
|
||||||
window.addEventListener('sun:cloud-permissions-changed',()=>{patchSupportPermissions();maintainSupport();syncMenuPermission();if(menuActive){renderMenuList();emptyMenuDetail();}});
|
window.addEventListener('sun:cloud-permissions-changed',()=>{patchSupportPermissions();maintainSupport();syncMenuPermission();if(menuActive){renderMenuList();emptyMenuDetail();}});
|
||||||
window.addEventListener('sun:cloud-state-applied',()=>{maintainSupport();syncMenuPermission();if(menuActive)renderMenuList();setTimeout(enhanceRoutePage,80)});
|
window.addEventListener('sun:cloud-state-applied',()=>{maintainSupport();syncMenuPermission();if(menuActive)renderMenuList();setTimeout(enhanceRoutePage,80)});
|
||||||
const mo=new MutationObserver(records=>{let route=false,supportBanner=false;for(const r of records){for(const n of r.addedNodes){if(n.nodeType!==1)continue;if(n.id==='routeContent'||n.querySelector?.('#routeContent')||n.closest?.('#sun-routes-view'))route=true;if(n.id==='sunDevSupportBannerV22'||n.querySelector?.('#sunDevSupportBannerV22'))supportBanner=true;}}if(route)setTimeout(enhanceRoutePage,30);if(supportBanner)setTimeout(renderSupportStatus,20);});mo.observe(document.documentElement,{childList:true,subtree:true});
|
const mo=new MutationObserver(records=>{let route=false,supportBanner=false;for(const r of records){for(const n of r.addedNodes){if(n.nodeType!==1)continue;if(n.id==='routeContent'||n.querySelector?.('#routeContent')||n.closest?.('#sun-routes-view'))route=true;if(n.id==='sunDevSupportBannerV22'||n.querySelector?.('#sunDevSupportBannerV22'))supportBanner=true;}}if(route)setTimeout(enhanceRoutePage,30);if(supportBanner)setTimeout(renderSupportStatus,20);});mo.observe(document.documentElement,{childList:true,subtree:true});
|
||||||
setInterval(()=>{maintainSupport();syncMenuPermission();if($('sun-routes-view')?.classList.contains('on'))enhanceRoutePage();},4000);
|
if(!maintenanceTimer)maintenanceTimer=setInterval(()=>{if(document.hidden)return;maintainSupport();syncMenuPermission();if($('sun-routes-view')?.classList.contains('on'))enhanceRoutePage();},8000);
|
||||||
window.SunOpsUXV1762={VERSION,RELEASE,refreshSupport,openMenu,showRouteOrder,enhanceRoutePage,checks:loadSelfChecks,disconnect:()=>mo.disconnect()};
|
window.SunOpsUXV1762={VERSION,RELEASE,refreshSupport,openMenu,showRouteOrder,enhanceRoutePage,checks:loadSelfChecks,disconnect:()=>{mo.disconnect();if(maintenanceTimer){clearInterval(maintenanceTimer);maintenanceTimer=0}if(supportTimer){clearInterval(supportTimer);supportTimer=0}if(waitTimer){clearTimeout(waitTimer);waitTimer=0}}};
|
||||||
}
|
}
|
||||||
const waitForApp=()=>{if(window.SunCloudV2&&document.querySelector('header nav')&&typeof window.editBox==='function')boot();else setTimeout(waitForApp,120)};
|
const waitForApp=()=>{if(window.SunCloudV2&&document.querySelector('header nav')&&typeof window.editBox==='function'){if(waitTimer){clearTimeout(waitTimer);waitTimer=0}boot();return}waitAttempts++;if(waitAttempts>=120){console.warn('[Caterium ops UX] app prerequisites timed out; waiting for a cloud-state event');return}waitTimer=setTimeout(waitForApp,250)};
|
||||||
|
window.addEventListener('sun:cloud-state-applied',()=>{if(!window.SunOpsUXV1762&&waitAttempts>=120){waitAttempts=0;waitForApp()}});
|
||||||
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',waitForApp,{once:true});else waitForApp();
|
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',waitForApp,{once:true});else waitForApp();
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
(()=>{
|
(()=>{
|
||||||
'use strict';
|
'use strict';
|
||||||
const VERSION='17.6.4';
|
const VERSION='17.6.5';
|
||||||
const RELEASE='20260907-v17-6-4-settings-orders-pdf-menu';
|
const RELEASE='20260908-v17-6-5-stability';
|
||||||
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;
|
||||||
@ -13,6 +13,8 @@
|
|||||||
if(root instanceof HTMLImageElement)tune(root);
|
if(root instanceof HTMLImageElement)tune(root);
|
||||||
root?.querySelectorAll?.('img').forEach(tune);
|
root?.querySelectorAll?.('img').forEach(tune);
|
||||||
};
|
};
|
||||||
|
const pendingImageRoots=new Set();let imageScanTimer=0;
|
||||||
|
function queueImageScan(root){if(!root||root.nodeType!==1)return;for(const existing of [...pendingImageRoots]){if(existing===root||existing.contains?.(root))return;if(root.contains?.(existing))pendingImageRoots.delete(existing)}pendingImageRoots.add(root);if(imageScanTimer)return;imageScanTimer=setTimeout(()=>{imageScanTimer=0;const roots=[...pendingImageRoots];pendingImageRoots.clear();roots.forEach(scan)},80);}
|
||||||
|
|
||||||
const MAX_FILE=15*1024*1024,TARGET=2*1024*1024,MAX_SIDE=2048;
|
const MAX_FILE=15*1024*1024,TARGET=2*1024*1024,MAX_SIDE=2048;
|
||||||
const guardedInputs=new WeakSet();
|
const guardedInputs=new WeakSet();
|
||||||
@ -103,12 +105,12 @@
|
|||||||
}
|
}
|
||||||
const start=()=>{
|
const start=()=>{
|
||||||
loadHotfix();loadOpsUX();loadUXFix();scan(document);startMemoryTimer();
|
loadHotfix();loadOpsUX();loadUXFix();scan(document);startMemoryTimer();
|
||||||
const mo=new MutationObserver(records=>{records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));});
|
const mo=new MutationObserver(records=>{records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)queueImageScan(n)}));});
|
||||||
mo.observe(document.documentElement,{childList:true,subtree:true});
|
mo.observe(document.documentElement,{childList:true,subtree:true});
|
||||||
document.addEventListener('click',e=>{if(e.target.closest('#sunDeveloperNavV22,[data-dev-tab],#sunDevRefresh'))scheduleMemoryRefresh(100,true)},true);
|
document.addEventListener('click',e=>{if(e.target.closest('#sunDeveloperNavV22,[data-dev-tab],#sunDevRefresh'))scheduleMemoryRefresh(100,true)},true);
|
||||||
window.addEventListener('sun:cloud-state-applied',()=>scheduleMemoryRefresh(180,true));
|
window.addEventListener('sun:cloud-state-applied',()=>scheduleMemoryRefresh(180,true));
|
||||||
document.addEventListener('visibilitychange',()=>{if(!document.hidden&&developerVisible())scheduleMemoryRefresh(50,false)});
|
document.addEventListener('visibilitychange',()=>{if(!document.hidden&&developerVisible())scheduleMemoryRefresh(50,false)});
|
||||||
window.SunPerformance={VERSION,scanImages:()=>scan(document),refreshDeveloperMemory:(force=true)=>enhanceDeveloperMemory({force}),loadHotfix,loadOpsUX,loadUXFix,disconnect:()=>{mo.disconnect();if(memoryTimer){clearInterval(memoryTimer);memoryTimer=0;}}};
|
window.SunPerformance={VERSION,scanImages:()=>scan(document),refreshDeveloperMemory:(force=true)=>enhanceDeveloperMemory({force}),loadHotfix,loadOpsUX,loadUXFix,disconnect:()=>{mo.disconnect();if(memoryTimer){clearInterval(memoryTimer);memoryTimer=0}if(imageScanTimer){clearTimeout(imageScanTimer);imageScanTimer=0}pendingImageRoots.clear();}};
|
||||||
};
|
};
|
||||||
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start,{once:true});else start();
|
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start,{once:true});else start();
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -197,7 +197,7 @@
|
|||||||
function maintain(){patchOrdersRender();patchOfferTemplateApi();patchOfferOpenExports();decorateMenuIcon();if(activeOfferOrderId)$('sunClientOfferModal')?.classList.contains('on')?ensureOfferPicker():clearActiveOffer()}
|
function maintain(){patchOrdersRender();patchOfferTemplateApi();patchOfferOpenExports();decorateMenuIcon();if(activeOfferOrderId)$('sunClientOfferModal')?.classList.contains('on')?ensureOfferPicker():clearActiveOffer()}
|
||||||
function startTimers(){
|
function startTimers(){
|
||||||
if(!autoTimer)autoTimer=setInterval(()=>{if(!document.hidden)autoCompleteOrders(Date.now(),{silent:false})},AUTO_POLL_MS);
|
if(!autoTimer)autoTimer=setInterval(()=>{if(!document.hidden)autoCompleteOrders(Date.now(),{silent:false})},AUTO_POLL_MS);
|
||||||
if(!maintainTimer)maintainTimer=setInterval(()=>{if(!document.hidden)maintain()},2000);
|
if(!maintainTimer)maintainTimer=setInterval(()=>{if(!document.hidden)maintain()},5000);
|
||||||
}
|
}
|
||||||
function onClickCapture(e){
|
function onClickCapture(e){
|
||||||
const direct=e.target?.closest?.('[data-sun-offer-id]');if(direct?.dataset?.sunOfferId)setActiveOffer(direct.dataset.sunOfferId);
|
const direct=e.target?.closest?.('[data-sun-offer-id]');if(direct?.dataset?.sunOfferId)setActiveOffer(direct.dataset.sunOfferId);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
|||||||
const CACHE='sun-catering-pwa-v69-20260907-v17-6-4-settings-orders-pdf-menu';
|
const CACHE='sun-catering-pwa-v70-20260908-v17-6-5-stability';
|
||||||
const VERSION='20260907-v17-6-4-settings-orders-pdf-menu';
|
const VERSION='20260908-v17-6-5-stability';
|
||||||
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}`,
|
||||||
|
|||||||
@ -155,10 +155,39 @@ 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.4');
|
expect(result.version).toBe('17.6.5');
|
||||||
});
|
});
|
||||||
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}));
|
||||||
expect(dims.scrollWidth).toBeLessThanOrEqual(dims.innerWidth+2); expect(dims.bodyWidth).toBeLessThanOrEqual(dims.innerWidth+2);
|
expect(dims.scrollWidth).toBeLessThanOrEqual(dims.innerWidth+2); expect(dims.bodyWidth).toBeLessThanOrEqual(dims.innerWidth+2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('v17.6.5 stays free of timer page errors during idle', async ({ page }, testInfo) => {
|
||||||
|
test.skip(testInfo.project.name!=='desktop');
|
||||||
|
const errors=[];page.on('pageerror',e=>errors.push(String(e)));
|
||||||
|
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
|
||||||
|
await page.waitForTimeout(5500);
|
||||||
|
expect(errors).toEqual([]);
|
||||||
|
const runtimeSource=fs.readFileSync(path.join(process.cwd(),'public','app-runtime.js'),'utf8');
|
||||||
|
expect(runtimeSource).toContain("const VERSION = '17.6.5'");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('v17.6.5 support refresh uses lightweight cloud API', async ({ page }) => {
|
||||||
|
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
|
||||||
|
await page.evaluate(()=>{try{window.SunOpsUXV1762?.disconnect?.()}catch(_){}document.getElementById('sunOpsUXV1762Script')?.remove();window.SunOpsUXV1762=undefined;window.__lightRefresh=0;window.__fullEnter=0;let nav=document.querySelector('header nav');if(!nav){const header=document.createElement('header');nav=document.createElement('nav');header.appendChild(nav);document.body.prepend(header)}window.SunCloudV2={getSupportMode:()=>({workspaceId:'support-test',name:'Тест'}),isSupportMode:()=>true,hasPermission:()=>false,getSession:()=>({user:{id:'dev'}}),refreshSupportWorkspace:async()=>{window.__lightRefresh++;return true},enterSupportWorkspace:async()=>{window.__fullEnter++;return true}};window.editBox=window.editBox||(()=>{});});
|
||||||
|
await injectCore(page,'ops-ux-v1762.js');
|
||||||
|
await page.waitForFunction(()=>Boolean(window.SunOpsUXV1762),null,{timeout:5000});
|
||||||
|
await page.evaluate(()=>window.SunOpsUXV1762.refreshSupport(true));
|
||||||
|
const counts=await page.evaluate(()=>({light:window.__lightRefresh,full:window.__fullEnter}));
|
||||||
|
expect(counts.light).toBe(1);expect(counts.full).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('v17.6.5 developer hotfix does not poll admin every second', async ({ page }) => {
|
||||||
|
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
|
||||||
|
await page.evaluate(()=>{window.SunHotfixV1763=undefined;window.__devChecks=0;window.SunCloudV2={getSession:()=>({user:{id:'dev'}}),getWorkspace:()=>null};window.SunDeveloperV22={open:()=>{},isPlatformAdmin:()=>false,checkPlatformAdmin:async()=>{window.__devChecks++;return false}};});
|
||||||
|
await injectCore(page,'hotfix-v1763.js','SunHotfixV1763');
|
||||||
|
await page.waitForTimeout(2600);
|
||||||
|
expect(await page.evaluate(()=>window.__devChecks)).toBeLessThanOrEqual(2);
|
||||||
|
});
|
||||||
@ -14,7 +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-4-settings-orders-pdf-menu')&&sw.includes('ux-fixes-v1764.js'),'service worker cache is v17.6.4');
|
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(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');
|
||||||
@ -24,7 +25,7 @@ check(performance.includes('hotfix-v1763.js')&&performance.includes('SunHotfixV1
|
|||||||
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');
|
||||||
check(performance.includes('ops-ux-v1762.js'),'operations UX module is loaded by performance core');
|
check(performance.includes('ops-ux-v1762.js'),'operations UX module is loaded by performance core');
|
||||||
check(ops.includes('supportReadPermission')&&ops.includes('SUPPORT_POLL_MS=12000'),'developer support read-only live refresh is versioned');
|
check(ops.includes('supportReadPermission')&&ops.includes('SUPPORT_POLL_MS=20000')&&ops.includes('refreshSupportWorkspace'),'developer support read-only refresh is lightweight and bounded');
|
||||||
check(ops.includes('sun-menu-editor-v1762')&&ops.includes('Премиум боксы'),'full-page menu editor is versioned');
|
check(ops.includes('sun-menu-editor-v1762')&&ops.includes('Премиум боксы'),'full-page menu editor is versioned');
|
||||||
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');
|
||||||
@ -38,6 +39,11 @@ check(!/sb_secret_[A-Za-z0-9_-]{20,}|service_role\s*[:=]\s*["'][A-Za-z0-9._-]{30
|
|||||||
check(lock.version===pkg.version&&lock.packages?.['']?.version===pkg.version,'package.json and package-lock.json versions match');
|
check(lock.version===pkg.version&&lock.packages?.['']?.version===pkg.version,'package.json and package-lock.json versions match');
|
||||||
check(releaseManifest.version===`v${pkg.version}`,'release manifest version matches package.json');
|
check(releaseManifest.version===`v${pkg.version}`,'release manifest version matches package.json');
|
||||||
check(releaseManifest.channel==='production','release manifest channel is production');
|
check(releaseManifest.channel==='production','release manifest channel is production');
|
||||||
check(String(releaseManifest.pwaCache||'').includes('v17-6-4'),'release manifest points to current PWA cache');
|
check(String(releaseManifest.pwaCache||'').includes('v17-6-5'),'release manifest points to current PWA cache');
|
||||||
check(['17.6.2','17.6.3','17.6.4'].every(v=>fs.existsSync(path.join(root,`docs/releases/V${v}-CHANGES.txt`))),'release notes exist for v17.6.2 through v17.6.4');
|
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(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');
|
||||||
if(bad)process.exit(1);
|
if(bad)process.exit(1);
|
||||||
|
|||||||
@ -27,16 +27,20 @@ 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-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(!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(!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('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('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=20000','supportReadPermission','refreshSupportWorkspace','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 ["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}`);
|
||||||
|
if(!hotfix.includes('checkPlatformAdmin?.(false)')||!hotfix.includes('},10000);'))fail('Developer fallback polling is still aggressive');else ok('Developer fallback polling is throttled');
|
||||||
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);
|
||||||
Loading…
Reference in New Issue
Block a user