Prepare Caterium v17.6.8 Developer Console UX
This commit is contained in:
parent
8e4890a913
commit
20d36f3a88
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"app": "Caterium",
|
"app": "Caterium",
|
||||||
"version": "v17.6.7",
|
"version": "v17.6.8",
|
||||||
"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": "v72-20260908-v17-6-7-classic-offer-pdf",
|
"pwaCache": "v73-20260908-v17-6-8-developer-console-ux",
|
||||||
"fullOfferDescriptions": true,
|
"fullOfferDescriptions": true,
|
||||||
"dynamicOfferRows": true,
|
"dynamicOfferRows": true,
|
||||||
"pdfOfferDescriptionFix": true,
|
"pdfOfferDescriptionFix": true,
|
||||||
@ -108,7 +108,7 @@
|
|||||||
"catalogCompositionTildaEndpoint": "getproduct",
|
"catalogCompositionTildaEndpoint": "getproduct",
|
||||||
"catalogCompositionPremiumForceRefresh": true,
|
"catalogCompositionPremiumForceRefresh": true,
|
||||||
"catalogCompositionCacheRequiresPremium": true,
|
"catalogCompositionCacheRequiresPremium": true,
|
||||||
"notes": "Restored the earlier presentation-style client PDF experience: 10 classic visual concepts plus 3 hidden archive templates. Preview and download are built from the exact same canvas pages.",
|
"notes": "Developer Console UX: fixed async DOM race errors, stable company numbering and deletion, accounts grouped by company, compact plan matrix, grouped production error journal.",
|
||||||
"catalogPhotoSources": 113,
|
"catalogPhotoSources": 113,
|
||||||
"catalogPhotosStoredInCatalog": true,
|
"catalogPhotosStoredInCatalog": true,
|
||||||
"catalogLegacyPhotosInCatalog": 60,
|
"catalogLegacyPhotosInCatalog": 60,
|
||||||
@ -233,7 +233,7 @@
|
|||||||
"normalUserStabilityHidden": true,
|
"normalUserStabilityHidden": true,
|
||||||
"authAdminEdgeFunctionDeployed": false,
|
"authAdminEdgeFunctionDeployed": false,
|
||||||
"authAdminEdgeFunctionNote": "Source included, but deployment was unavailable in this build environment. All-account listing uses existing server-side sun_platform_list_users RPC; global Auth ban/recovery controls remain unavailable until protected backend function is deployed.",
|
"authAdminEdgeFunctionNote": "Source included, but deployment was unavailable in this build environment. All-account listing uses existing server-side sun_platform_list_users RPC; global Auth ban/recovery controls remain unavailable until protected backend function is deployed.",
|
||||||
"developerSqlMigration": "SUPABASE-DEVELOPER-V22.sql",
|
"developerSqlMigration": "SUPABASE-V17.6.8-DEVELOPER-CONSOLE-UX.sql",
|
||||||
"developerMfaRecovery": true,
|
"developerMfaRecovery": true,
|
||||||
"registrationTabs": true,
|
"registrationTabs": true,
|
||||||
"registrationCompanyNameRequired": true,
|
"registrationCompanyNameRequired": true,
|
||||||
@ -246,7 +246,7 @@
|
|||||||
"signupTrial": "14-day Full",
|
"signupTrial": "14-day Full",
|
||||||
"developerMfaInputSelectorFixed": true,
|
"developerMfaInputSelectorFixed": true,
|
||||||
"developerMfaRepeatedValidationToastsFixed": true,
|
"developerMfaRepeatedValidationToastsFixed": true,
|
||||||
"release": "20260908-v17-6-7-classic-offer-pdf",
|
"release": "20260908-v17-6-8-developer-console-ux",
|
||||||
"registrationFlow": "email-password-confirm-company-auto-login",
|
"registrationFlow": "email-password-confirm-company-auto-login",
|
||||||
"emailConfirmationRequired": false,
|
"emailConfirmationRequired": false,
|
||||||
"employeeInviteLinks": false,
|
"employeeInviteLinks": false,
|
||||||
@ -345,5 +345,13 @@
|
|||||||
"emerald-gold",
|
"emerald-gold",
|
||||||
"neon-emerald"
|
"neon-emerald"
|
||||||
],
|
],
|
||||||
"classicOfferPdfCoverPages": true
|
"classicOfferPdfCoverPages": true,
|
||||||
|
"developerCompanyNumbers": true,
|
||||||
|
"developerCompanyDelete": true,
|
||||||
|
"developerAccountsGroupedByCompany": true,
|
||||||
|
"developerPlansCompactMatrix": true,
|
||||||
|
"developerJournalGroupedErrors": true,
|
||||||
|
"developerJournalFiltersLocalCopies": true,
|
||||||
|
"developerDomRaceFix": true,
|
||||||
|
"developerConsoleUXModule": "core/developer-console-v1768.js"
|
||||||
}
|
}
|
||||||
|
|||||||
9
docs/releases/V17.6.8-CHANGES.txt
Normal file
9
docs/releases/V17.6.8-CHANGES.txt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
Caterium v17.6.8 — Developer Console UX
|
||||||
|
|
||||||
|
- Исправлена гонка DOM при открытии и переключении вкладок кабинета разработчика, вызывавшая className/null и reading add.
|
||||||
|
- Компаниям присваивается постоянный последовательный номер, который не переиспользуется после удаления.
|
||||||
|
- В разделе «Компании» добавлено безопасное удаление с двойным подтверждением. Связанные данные компании удаляются каскадно, Auth-аккаунты сохраняются.
|
||||||
|
- «Аккаунты» сгруппированы по компаниям: сначала выбирается компания, затем видны только её пользователи. Добавлена группа «Без компании».
|
||||||
|
- «Тарифы» переведены на компактную матрицу функций вместо длинных повторяющихся списков.
|
||||||
|
- «Журнал» группирует одинаковые ошибки и отделяет актуальные production-ошибки от шума старых локальных file:// копий.
|
||||||
|
- Добавлен серверный RPC для сгруппированного журнала, постоянной нумерации и удаления компаний.
|
||||||
52
package-lock.json
generated
52
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "caterium-app",
|
"name": "caterium-app",
|
||||||
"version": "17.6.7",
|
"version": "17.6.8",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "caterium-app",
|
"name": "caterium-app",
|
||||||
"version": "17.6.7",
|
"version": "17.6.8",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.51.0",
|
"@playwright/test": "^1.51.0",
|
||||||
"http-server": "^14.1.1",
|
"http-server": "^14.1.1",
|
||||||
@ -708,9 +708,6 @@
|
|||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -728,9 +725,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -748,9 +742,6 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -768,9 +759,6 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -788,9 +776,6 @@
|
|||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -808,9 +793,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -828,9 +810,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -848,9 +827,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -868,9 +844,6 @@
|
|||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -894,9 +867,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -920,9 +890,6 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -946,9 +913,6 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -972,9 +936,6 @@
|
|||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -998,9 +959,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -1024,9 +982,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -1050,9 +1005,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "caterium-app",
|
"name": "caterium-app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "17.6.7",
|
"version": "17.6.8",
|
||||||
"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 && node --check public/core/classic-offer-pdf-v1767.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 && node --check public/core/classic-offer-pdf-v1767.js && node --check public/core/developer-console-v1768.js",
|
||||||
"test:static": "node tests/static-security.mjs",
|
"test:static": "node tests/static-security.mjs",
|
||||||
"check:release": "node tests/release-check.mjs",
|
"check:release": "node tests/release-check.mjs",
|
||||||
"check:deploy": "npm run check:syntax && npm run test:static && npm run check:release",
|
"check:deploy": "npm run check:syntax && npm run test:static && npm run check:release",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
(()=>{
|
(()=>{
|
||||||
'use strict';
|
'use strict';
|
||||||
const VERSION='17.6.7';
|
const VERSION='17.6.8';
|
||||||
const RELEASE='20260908-v17-6-7-classic-offer-pdf';
|
const RELEASE='20260908-v17-6-8-developer-console-ux';
|
||||||
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;
|
||||||
@ -103,14 +103,18 @@
|
|||||||
if(window.SunUXFixV1764||document.getElementById('sunUXFixV1764Script'))return;
|
if(window.SunUXFixV1764||document.getElementById('sunUXFixV1764Script'))return;
|
||||||
const script=document.createElement('script');script.id='sunUXFixV1764Script';script.src=`core/ux-fixes-v1764.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль ux-fixes-v1764.js');document.head.appendChild(script);
|
const script=document.createElement('script');script.id='sunUXFixV1764Script';script.src=`core/ux-fixes-v1764.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль ux-fixes-v1764.js');document.head.appendChild(script);
|
||||||
}
|
}
|
||||||
|
function loadDeveloperUX(){
|
||||||
|
if(window.SunDeveloperUXV1768||document.getElementById('sunDeveloperUXV1768Script'))return;
|
||||||
|
const script=document.createElement('script');script.id='sunDeveloperUXV1768Script';script.src=`core/developer-console-v1768.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль developer-console-v1768.js');document.head.appendChild(script);
|
||||||
|
}
|
||||||
const start=()=>{
|
const start=()=>{
|
||||||
loadHotfix();loadOpsUX();loadUXFix();scan(document);startMemoryTimer();
|
loadHotfix();loadOpsUX();loadUXFix();loadDeveloperUX();scan(document);startMemoryTimer();
|
||||||
const mo=new MutationObserver(records=>{records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)queueImageScan(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}if(imageScanTimer){clearTimeout(imageScanTimer);imageScanTimer=0}pendingImageRoots.clear();}};
|
window.SunPerformance={VERSION,scanImages:()=>scan(document),refreshDeveloperMemory:(force=true)=>enhanceDeveloperMemory({force}),loadHotfix,loadOpsUX,loadUXFix,loadDeveloperUX,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();
|
||||||
})();
|
})();
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -1,8 +1,8 @@
|
|||||||
const CACHE='sun-catering-pwa-v72-20260908-v17-6-7-classic-offer-pdf';
|
const CACHE='sun-catering-pwa-v73-20260908-v17-6-8-developer-console-ux';
|
||||||
const VERSION='20260908-v17-6-7-classic-offer-pdf';
|
const VERSION='20260908-v17-6-8-developer-console-ux';
|
||||||
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}`,`./core/classic-offer-pdf-v1767.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}`,`./core/classic-offer-pdf-v1767.js?v=${VERSION}`,`./core/developer-console-v1768.js?v=${VERSION}`,`./legacy/bootstrap.js?v=${VERSION}`,`./app-runtime.js?v=${VERSION}`,
|
||||||
'./offer-gallery/001.jpg','./offer-gallery/002.jpg',
|
'./offer-gallery/001.jpg','./offer-gallery/002.jpg',
|
||||||
'./catalog/001.jpg','./catalog/002.jpg','./catalog/003.jpg',
|
'./catalog/001.jpg','./catalog/002.jpg','./catalog/003.jpg',
|
||||||
'./sun-logo.png','./caterium-login-logo.png','./pwa-icon-192.png','./pwa-icon-512.png','./manifest.webmanifest',
|
'./sun-logo.png','./caterium-login-logo.png','./pwa-icon-192.png','./pwa-icon-512.png','./manifest.webmanifest',
|
||||||
|
|||||||
@ -156,7 +156,7 @@ test('Developer Console memory refresh is bounded and does not react to its own
|
|||||||
const result=await page.evaluate(()=>({count:window.__rpcCount,box:document.querySelectorAll('#sunDevMemoryV1761').length,version:document.getElementById('sunDevReleaseVersion')?.textContent}));
|
const result=await page.evaluate(()=>({count:window.__rpcCount,box:document.querySelectorAll('#sunDevMemoryV1761').length,version:document.getElementById('sunDevReleaseVersion')?.textContent}));
|
||||||
expect(result.count).toBeLessThanOrEqual(2);
|
expect(result.count).toBeLessThanOrEqual(2);
|
||||||
expect(result.box).toBe(1);
|
expect(result.box).toBe(1);
|
||||||
expect(result.version).toBe('17.6.7');
|
expect(result.version).toBe('17.6.8');
|
||||||
});
|
});
|
||||||
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);
|
||||||
@ -224,3 +224,19 @@ test('v17.6.7 classic client PDF renders distinctive cover and keeps preview/dow
|
|||||||
});
|
});
|
||||||
expect(result.count).toBe(2);expect(result.id).toBe('event-ticket');expect(result.cover).toBe('1');expect(result.classic).toBe(10);expect(result.archive).toBe(3);
|
expect(result.count).toBe(2);expect(result.id).toBe('event-ticket');expect(result.cover).toBe('1');expect(result.classic).toBe(10);expect(result.archive).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('v17.6.8 Developer Console UX module boots', async ({ page }) => {
|
||||||
|
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
|
||||||
|
const source=fs.readFileSync(path.join(process.cwd(),'public','core','developer-console-v1768.js'),'utf8');
|
||||||
|
await page.evaluate(()=>{
|
||||||
|
document.body.innerHTML='<section id="sun-developer-console-v22" class="view on"><div class="sun-dev-tabs"><button data-dev-tab="overview" class="on">Обзор</button><button data-dev-tab="companies">Компании</button><button data-dev-tab="accounts">Аккаунты</button><button data-dev-tab="plans">Тарифы</button><button data-dev-tab="journal">Журнал</button></div><div id="sunDevBody"></div></section>';
|
||||||
|
window.SunDeveloperV22={open:async()=>true,activateTab:async()=>true};
|
||||||
|
window.SunCloudV2={getClient:()=>null};
|
||||||
|
window.SunEnterprise={toast:()=>null};
|
||||||
|
});
|
||||||
|
await page.addScriptTag({content:source});
|
||||||
|
await page.waitForFunction(()=>Boolean(window.SunDeveloperUXV1768),null,{timeout:5000});
|
||||||
|
const result=await page.evaluate(()=>window.SunDeveloperUXV1768.checks());
|
||||||
|
expect(result.version).toBe('17.6.8');
|
||||||
|
});
|
||||||
|
|||||||
@ -4,7 +4,7 @@ const root=process.cwd(), pub=path.join(root,'public');
|
|||||||
const read=p=>fs.readFileSync(path.join(pub,p),'utf8');
|
const read=p=>fs.readFileSync(path.join(pub,p),'utf8');
|
||||||
const readRoot=p=>fs.readFileSync(path.join(root,p),'utf8');
|
const readRoot=p=>fs.readFileSync(path.join(root,p),'utf8');
|
||||||
let bad=0;const check=(v,m)=>{console.log(`${v?'OK':'FAIL'}: ${m}`);if(!v)bad++};
|
let bad=0;const check=(v,m)=>{console.log(`${v?'OK':'FAIL'}: ${m}`);if(!v)bad++};
|
||||||
const index=read('index.html'),runtime=read('app-runtime.js'),sw=read('service-worker.js'),css=read('core/stability-v1760.css'),performance=read('core/performance.js'),ops=read('core/ops-ux-v1762.js'),hotfix=read('core/hotfix-v1763.js'),ux=read('core/ux-fixes-v1764.js'),classic=read('core/classic-offer-pdf-v1767.js');
|
const index=read('index.html'),runtime=read('app-runtime.js'),sw=read('service-worker.js'),css=read('core/stability-v1760.css'),performance=read('core/performance.js'),ops=read('core/ops-ux-v1762.js'),hotfix=read('core/hotfix-v1763.js'),ux=read('core/ux-fixes-v1764.js'),classic=read('core/classic-offer-pdf-v1767.js'),developerUX=read('core/developer-console-v1768.js');
|
||||||
const pkg=JSON.parse(readRoot('package.json'));
|
const pkg=JSON.parse(readRoot('package.json'));
|
||||||
const lock=JSON.parse(readRoot('package-lock.json'));
|
const lock=JSON.parse(readRoot('package-lock.json'));
|
||||||
const releaseManifest=JSON.parse(readRoot('docs/release-manifest.json'));
|
const releaseManifest=JSON.parse(readRoot('docs/release-manifest.json'));
|
||||||
@ -14,8 +14,8 @@ check(!index.includes('offer-gallery-data.js'),'blocking Base64 gallery absent')
|
|||||||
check((runtime.match(/\/Type \/Catalog/g)||[]).length===0,'runtime contains no PDF binary writer');
|
check((runtime.match(/\/Type \/Catalog/g)||[]).length===0,'runtime contains no PDF binary writer');
|
||||||
check(read('core/pdf-engine.js').includes('595.28')&&read('core/pdf-engine.js').includes('841.89'),'PDF engine uses A4 MediaBox');
|
check(read('core/pdf-engine.js').includes('595.28')&&read('core/pdf-engine.js').includes('841.89'),'PDF engine uses A4 MediaBox');
|
||||||
check([...index.matchAll(/@page\{([^}]*)\}/g)].every(m=>/size:A4/i.test(m[1])),'compact @page rules use A4');
|
check([...index.matchAll(/@page\{([^}]*)\}/g)].every(m=>/size:A4/i.test(m[1])),'compact @page rules use A4');
|
||||||
check(sw.includes('v72-20260908-v17-6-7-classic-offer-pdf')&&sw.includes('classic-offer-pdf-v1767.js'),'service worker cache is v17.6.7');
|
check(sw.includes('v73-20260908-v17-6-8-developer-console-ux')&&sw.includes('classic-offer-pdf-v1767.js')&&sw.includes('developer-console-v1768.js'),'service worker cache is v17.6.8');
|
||||||
check(index.includes('20260908-v17-6-7-classic-offer-pdf')&&index.includes('classic-offer-pdf-v1767.js')&&!index.includes('20260907-v17-6-0-stability-security'),'index cache-busting points to v17.6.7');
|
check(index.includes('20260908-v17-6-8-developer-console-ux')&&index.includes('classic-offer-pdf-v1767.js')&&!index.includes('20260907-v17-6-0-stability-security'),'index cache-busting points to v17.6.8');
|
||||||
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');
|
||||||
@ -39,8 +39,8 @@ 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('v72-20260908-v17-6-7-classic-offer-pdf'),'release manifest points to current PWA cache');
|
check(String(releaseManifest.pwaCache||'').includes('v73-20260908-v17-6-8-developer-console-ux'),'release manifest points to current PWA cache');
|
||||||
check(['17.6.2','17.6.3','17.6.4','17.6.5','17.6.6','17.6.7'].every(v=>fs.existsSync(path.join(root,`docs/releases/V${v}-CHANGES.txt`))),'release notes exist through v17.6.7');
|
check(['17.6.2','17.6.3','17.6.4','17.6.5','17.6.6','17.6.7','17.6.8'].every(v=>fs.existsSync(path.join(root,`docs/releases/V${v}-CHANGES.txt`))),'release notes exist through v17.6.8');
|
||||||
check(runtime.includes('CLOUD_RPC_TIMEOUT_MS=12000')&&runtime.includes('CLOUD_CONFLICT_MAX_RETRIES=4')&&runtime.includes('retryCount'),'cloud sync has timeout and capped exponential conflict retries');
|
check(runtime.includes('CLOUD_RPC_TIMEOUT_MS=12000')&&runtime.includes('CLOUD_CONFLICT_MAX_RETRIES=4')&&runtime.includes('retryCount'),'cloud sync has timeout and capped exponential conflict retries');
|
||||||
check(runtime.includes("const VERSION = '17.6.5'")&&runtime.includes('ERROR_DEDUPE_MS=5*60*1000')&&runtime.includes('mirrorBusy=false')&&runtime.includes('backupBusy=false'),'stability logger uses current version, dedupe and single-flight guards');
|
check(runtime.includes("const VERSION = '17.6.5'")&&runtime.includes('ERROR_DEDUPE_MS=5*60*1000')&&runtime.includes('mirrorBusy=false')&&runtime.includes('backupBusy=false'),'stability logger uses current version, dedupe and single-flight guards');
|
||||||
check(runtime.includes('refreshSupportWorkspace')&&runtime.includes('sun_dev_support_snapshot'),'cloud exposes lightweight read-only support refresh');
|
check(runtime.includes('refreshSupportWorkspace')&&runtime.includes('sun_dev_support_snapshot'),'cloud exposes lightweight read-only support refresh');
|
||||||
@ -49,11 +49,15 @@ check(performance.includes('pendingImageRoots')&&performance.includes('queueImag
|
|||||||
check(runtime.includes('explicitTemplate')&&runtime.includes("OFFER_TEMPLATE_IDS.has(explicitTemplate)"),'per-order proposal template survives render and PDF');
|
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')"),'existing proposal layout sequences remain available');
|
check(runtime.includes("else if(id==='midnight-glass')")&&runtime.includes("else if(id==='emerald-gold')")&&runtime.includes("if(id==='editorial-grid')"),'existing proposal layout sequences remain available');
|
||||||
check(runtime.includes('data-template-mini')&&ux.includes('data-template-mini'),'global and per-offer selectors show structural PDF previews');
|
check(runtime.includes('data-template-mini')&&ux.includes('data-template-mini'),'global and per-offer selectors show structural PDF previews');
|
||||||
check(pkg.version==='17.6.7','package version is v17.6.7');
|
check(pkg.version==='17.6.8','package version is v17.6.8');
|
||||||
check(classic.includes("const VERSION='17.6.7'")&&classic.includes('CLASSIC_IDS')&&classic.includes('ARCHIVE_IDS'),'classic proposal PDF module is v17.6.7');
|
check(classic.includes("const VERSION='17.6.7'")&&classic.includes('CLASSIC_IDS')&&classic.includes('ARCHIVE_IDS'),'classic proposal PDF module is v17.6.7');
|
||||||
check(runtime.includes("'warm-sun'")&&runtime.includes("'bento-cards'")&&runtime.includes("'event-story'")&&runtime.includes("'personal-letter'")&&runtime.includes("'event-ticket'")&&runtime.includes("'solar-experience'"),'10 classic proposal designs are available');
|
check(runtime.includes("'warm-sun'")&&runtime.includes("'bento-cards'")&&runtime.includes("'event-story'")&&runtime.includes("'personal-letter'")&&runtime.includes("'event-ticket'")&&runtime.includes("'solar-experience'"),'10 classic proposal designs are available');
|
||||||
check(runtime.includes("'midnight-compact'")&&runtime.includes("'black-gold'")&&runtime.includes("'neon-emerald'"),'hidden archive proposal templates are restored');
|
check(runtime.includes("'midnight-compact'")&&runtime.includes("'black-gold'")&&runtime.includes("'neon-emerald'"),'hidden archive proposal templates are restored');
|
||||||
check(runtime.includes('SunClassicOfferPDFV1767')&&runtime.includes('renderOfferPdfPagesBase'),'classic renderer wraps the same PDF pages used by preview/download');
|
check(runtime.includes('SunClassicOfferPDFV1767')&&runtime.includes('renderOfferPdfPagesBase'),'classic renderer wraps the same PDF pages used by preview/download');
|
||||||
check(classic.includes('data-sun-classic-cover')&&classic.includes('renderPages'),'classic renderer adds real A4 cover pages');
|
check(classic.includes('data-sun-classic-cover')&&classic.includes('renderPages'),'classic renderer adds real A4 cover pages');
|
||||||
check(releaseManifest.offerTemplates===13&&releaseManifest.classicOfferTemplates===10,'release manifest exposes 10 classic + 3 archive templates');
|
check(releaseManifest.offerTemplates===13&&releaseManifest.classicOfferTemplates===10,'release manifest exposes 10 classic + 3 archive templates');
|
||||||
|
check(developerUX.includes("const VERSION='17.6.8'")&&developerUX.includes('sun_dev_delete_company_v1768')&&developerUX.includes('sun_dev_error_groups_v1768'),'Developer Console v17.6.8 module is versioned');
|
||||||
|
check(developerUX.includes('selectedAccountsWorkspace')&&developerUX.includes('sun-dev-plan-matrix'),'accounts are grouped by company and plans use compact matrix');
|
||||||
|
check(developerUX.includes('KNOWN_DOM_RACE')&&developerUX.includes('prepareAsyncRoots'),'Developer Console async DOM race is guarded');
|
||||||
|
check(releaseManifest.developerCompanyNumbers===true&&releaseManifest.developerCompanyDelete===true&&releaseManifest.developerAccountsGroupedByCompany===true&&releaseManifest.developerPlansCompactMatrix===true&&releaseManifest.developerJournalGroupedErrors===true,'release manifest records Developer Console UX changes');
|
||||||
if(bad)process.exit(1);
|
if(bad)process.exit(1);
|
||||||
|
|||||||
@ -6,7 +6,7 @@ const readPub=p=>fs.readFileSync(path.join(pub,p),'utf8');
|
|||||||
const readRoot=p=>fs.readFileSync(path.join(root,p),'utf8');
|
const readRoot=p=>fs.readFileSync(path.join(root,p),'utf8');
|
||||||
const fail=m=>{console.error('FAIL:',m);process.exitCode=1};
|
const fail=m=>{console.error('FAIL:',m);process.exitCode=1};
|
||||||
const ok=m=>console.log('OK:',m);
|
const ok=m=>console.log('OK:',m);
|
||||||
const html=readPub('index.html'),legacy=readPub('legacy/bootstrap.js'),runtime=readPub('app-runtime.js'),safe=readPub('core/sun-safe.js'),sw=readPub('service-worker.js'),performance=readPub('core/performance.js'),ops=readPub('core/ops-ux-v1762.js'),hotfix=readPub('core/hotfix-v1763.js'),ux=readPub('core/ux-fixes-v1764.js'),classic=readPub('core/classic-offer-pdf-v1767.js');
|
const html=readPub('index.html'),legacy=readPub('legacy/bootstrap.js'),runtime=readPub('app-runtime.js'),safe=readPub('core/sun-safe.js'),sw=readPub('service-worker.js'),performance=readPub('core/performance.js'),ops=readPub('core/ops-ux-v1762.js'),hotfix=readPub('core/hotfix-v1763.js'),ux=readPub('core/ux-fixes-v1764.js'),classic=readPub('core/classic-offer-pdf-v1767.js'),developerUX=readPub('core/developer-console-v1768.js');
|
||||||
if(!html.includes('core/sun-safe.js'))fail('SunSafe must load before legacy modules');else ok('shared SunSafe loaded');
|
if(!html.includes('core/sun-safe.js'))fail('SunSafe must load before legacy modules');else ok('shared SunSafe loaded');
|
||||||
if(html.includes('offer-gallery-data.js')||fs.existsSync(path.join(pub,'offer-gallery-data.js')))fail('blocking offer-gallery-data.js still present');else ok('base64 gallery removed');
|
if(html.includes('offer-gallery-data.js')||fs.existsSync(path.join(pub,'offer-gallery-data.js')))fail('blocking offer-gallery-data.js still present');else ok('base64 gallery removed');
|
||||||
for(const raw of ['<span>${b.name}</span>','<span>${o.event}</span>','<span>${o.address||','value="${x[0]}"','value="${x[2]}"']) if(legacy.includes(raw)) fail(`legacy bootstrap contains raw HTML interpolation ${raw}`);
|
for(const raw of ['<span>${b.name}</span>','<span>${o.event}</span>','<span>${o.address||','value="${x[0]}"','value="${x[2]}"']) if(legacy.includes(raw)) fail(`legacy bootstrap contains raw HTML interpolation ${raw}`);
|
||||||
@ -27,8 +27,8 @@ 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-7-classic-offer-pdf')||!sw.includes('classic-offer-pdf-v1767.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated to v17.6.7');
|
if(!sw.includes('v17-6-8-developer-console-ux')||!sw.includes('developer-console-v1768.js')||!sw.includes('classic-offer-pdf-v1767.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated to v17.6.8');
|
||||||
if(html.includes('20260907-v17-6-0-stability-security')||!html.includes('20260908-v17-6-7-classic-offer-pdf')||!html.includes('classic-offer-pdf-v1767.js'))fail('index still serves stale core asset version');else ok('index cache-busting is current');
|
if(html.includes('20260907-v17-6-0-stability-security')||!html.includes('20260908-v17-6-8-developer-console-ux')||!html.includes('classic-offer-pdf-v1767.js'))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');
|
||||||
@ -44,5 +44,6 @@ for(const marker of ['CLOUD_RPC_TIMEOUT_MS=12000','CLOUD_CONFLICT_MAX_RETRIES=4'
|
|||||||
if(!hotfix.includes('checkPlatformAdmin?.(false)')||!hotfix.includes('},10000);'))fail('Developer fallback polling is still aggressive');else ok('Developer fallback polling is throttled');
|
if(!hotfix.includes('checkPlatformAdmin?.(false)')||!hotfix.includes('},10000);'))fail('Developer fallback polling is still aggressive');else ok('Developer fallback polling is throttled');
|
||||||
if(!ux.includes('sunMenuIconV1766')||!ux.includes('sun-offer-template-mini-editorial-grid')||!runtime.includes('explicitTemplate'))fail('v17.6.6 proposal/menu markers missing');else ok('v17.6.6 proposal/menu markers present');
|
if(!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(!classic.includes("const VERSION='17.6.7'")||!classic.includes('CLASSIC_IDS')||!classic.includes('ARCHIVE_IDS')||!classic.includes('renderPages'))fail('v17.6.7 classic PDF module missing');else ok('v17.6.7 classic PDF module present');
|
if(!classic.includes("const VERSION='17.6.7'")||!classic.includes('CLASSIC_IDS')||!classic.includes('ARCHIVE_IDS')||!classic.includes('renderPages'))fail('v17.6.7 classic PDF module missing');else ok('v17.6.7 classic PDF module present');
|
||||||
if(/service_role\s*[:=]\s*['"][A-Za-z0-9._-]{20,}/i.test(ops+hotfix+ux+classic)||/eyJ[a-zA-Z0-9_-]{30,}/.test(ops+hotfix+ux+classic))fail('possible secret in ops/hotfix/ux/classic module');else ok('ops/hotfix/ux/classic modules have no hard-coded secret');
|
if(!developerUX.includes("const VERSION='17.6.8'")||!developerUX.includes('sun_dev_delete_company_v1768')||!developerUX.includes('sun_dev_error_groups_v1768')||!developerUX.includes('selectedAccountsWorkspace')||!developerUX.includes('sun-dev-plan-matrix')||!developerUX.includes('KNOWN_DOM_RACE'))fail('v17.6.8 Developer Console UX module missing');else ok('v17.6.8 Developer Console UX module present');
|
||||||
|
if(/service_role\s*[:=]\s*['"][A-Za-z0-9._-]{20,}/i.test(ops+hotfix+ux+classic+developerUX)||/eyJ[a-zA-Z0-9_-]{30,}/.test(ops+hotfix+ux+classic+developerUX))fail('possible secret in ops/hotfix/ux/classic/developer module');else ok('ops/hotfix/ux/classic/developer 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