diff --git a/.github/workflows/deploy-timeweb.yml b/.github/workflows/deploy-timeweb.yml
index 118c2df..8a8221a 100644
--- a/.github/workflows/deploy-timeweb.yml
+++ b/.github/workflows/deploy-timeweb.yml
@@ -43,6 +43,9 @@ jobs:
files=(
index.html
app-runtime.js
+ core/account-center-v1780.js
+ core/company-branding.js
+ core/sun-safe.js
core/login-signature-v1776.js
core/login-signature-v1776.css
core/help-center.js
@@ -124,6 +127,9 @@ jobs:
- name: Check the published single-page banquet menu
timeout-minutes: 4
run: node tests/production-banquet-client-menu.mjs
+ - name: Check published employee session and mobile logout
+ timeout-minutes: 4
+ run: node tests/production-employee-session.mjs
- name: Save production UI verification
if: always()
uses: actions/upload-artifact@v4
diff --git a/docs/releases/2026-09-20-EMPLOYEE-SESSION.md b/docs/releases/2026-09-20-EMPLOYEE-SESSION.md
new file mode 100644
index 0000000..94927ac
--- /dev/null
+++ b/docs/releases/2026-09-20-EMPLOYEE-SESSION.md
@@ -0,0 +1,46 @@
+# Company employee sessions and mobile logout
+
+## Application changes
+
+Notification read flags now use a device-local key scoped to the authenticated
+user and selected company. They do not enter the company synchronization payload.
+Older remote `sunReadNotificationsV1` values are preserved, not overwritten or
+removed by an employee. Read-only sections are taken from the remote snapshot
+instead of uploading bootstrap defaults or empty caches as staff changes. Order
+reconciliation respects the existing create/edit/delete permissions separately.
+The server still enforces all permissions; no role or membership is granted.
+
+On mobile, the main header contains labelled Profile and Logout buttons with SVG
+icons. Profile also has a sticky top Logout button, including while its detail RPC
+is pending or unavailable. Logout uses the existing bounded local-device sign-out
+and tenant-cache preservation. Late account/company replies cannot restore stale
+profile or sidebar information.
+
+## Database change — must be deployed separately
+
+Apply `supabase/migrations/20260920104500_workspace_sidebar_brand.sql` to the
+Caterium database. It adds a membership-checked, selected-workspace sidebar RPC.
+An exclusive branding assignment is associated with its owner's company only
+when that company is unambiguous. Multiple owned companies need an explicit
+assignment by an authorized database administrator, never a guessed name match.
+Active confirmed employees of the assigned company receive the Solnce sidebar.
+The old owner-only RPC remains compatible with older clients.
+
+Publishing application assets does NOT apply SQL migrations. Until the database
+migration is applied, the client safely falls back to the old owner-only RPC;
+this release must not be reported as fixing employee branding on that server.
+No company catalog, order, client, membership, or production account is changed
+by the migration. The separate ai-staff project is outside this change.
+
+## Verification scope
+
+Browser scenarios use synthetic company accounts and do not access production
+business data. They cover an empty employee device, pre-existing baselines,
+allowed order edits with forbidden UI preferences, record-level permissions,
+personal notification isolation, selected-workspace branding, old-server
+compatibility, late replies, and visible mobile logout with unavailable details.
+The recovery database suite checks the new RPC against actual SQL permissions.
+
+The reported employee's actual company membership is a separate diagnosis and
+requires the exact new email and an authorized server/account read. Empty UI alone
+is not evidence that company orders were deleted.
diff --git a/public/app-runtime.js b/public/app-runtime.js
index e84e4c8..8b16f70 100644
--- a/public/app-runtime.js
+++ b/public/app-runtime.js
@@ -2308,7 +2308,7 @@ window.SUN_LEGACY_CATALOG_V175=[];
if (!key.startsWith('sun')) return false;
if (key === CONFIG_KEY || key === CLIENT_ID_KEY) return false;
if (key === 'sunUsersV1' || key === 'sunSessionUserV1') return false;
- if (key === 'sunFallbackBackupsV1') return false;
+ if (key === 'sunFallbackBackupsV1' || key === 'sunReadNotificationsV1') return false;
if (key === 'sunStaticMapGeocodeCacheV2') return false;
if (key === 'sunCloudSyncMetaV1' || key === 'sunCloudSyncTokenV1' || key === 'sunCloudSyncWorkspaceV1') return false;
if (key === 'sunOrdersView' || key === 'sunCalendarMode' || key === 'sunCatalogViewModeV1' || key === 'sunNewOrderSplitRatioV5') return false;
@@ -2509,7 +2509,7 @@ window.SUN_LEGACY_CATALOG_V175=[];
}
function setSignOutUiBusy(busy) {
- const selectors=['#sunCloudSignOutV2','#sunLogoutBtn','#sunGateSignOutV3','#sunCloudUsePasswordV3','[data-signout]','[data-saas-signout]'];
+ const selectors=['#cacLogout','#cacHeaderLogout','#cateriumMobileLogout','#sunCloudSignOutV2','#sunLogoutBtn','#sunGateSignOutV3','#sunCloudUsePasswordV3','[data-signout]','[data-saas-signout]'];
document.querySelectorAll(selectors.join(',')).forEach(btn=>{
if(!(btn instanceof HTMLButtonElement))return;
if(busy){btn.dataset.sunOldText=btn.textContent||'';btn.disabled=true;btn.textContent='Выходим…';}
@@ -2641,6 +2641,22 @@ window.SUN_LEGACY_CATALOG_V175=[];
return clone(local);
}
+ // Match the server's section permissions without expanding a user's rights.
+ // Bootstrap/default UI writes in a read-only section cannot block a valid order
+ // save or turn an omitted company setting into a deletion.
+ function canWriteStorageKey(key) {
+ if(key==='sunOrders')return ['orders.create','orders.edit','orders.delete'].some(hasPermission);
+ if(key==='sunAuditLogV1')return Boolean(workspace&&workspace.is_active!==false);
+ const permissions={sunBoxes:'catalog.edit',sunOfficialCatalogVersion:'catalog.edit',
+ sunClientLoyaltyV1:'clients.edit',sunClientCommunicationV1:'clients.edit',
+ sunFinanceRecordsV2:'money.edit',sunStock:'stock.edit',sunStockMoves:'stock.edit',
+ sunEmployees:'team.edit',sunSuppliers:'suppliers.edit',sunPromoCodesV1:'mailings.edit'};
+ return hasPermission(permissions[key]||(key.startsWith('sunRoute')?'routes.edit':key.startsWith('sunMarketing')?'mailings.edit':'settings.edit'));
+ }
+ function equalSyncPayload(a,b){
+ const filtered=p=>({...p,storage:Object.fromEntries(Object.entries(p?.storage||{}).filter(([key])=>shouldSyncKey(key)))});
+ return equal(filtered(a),filtered(b));
+ }
function mergePayloads(basePayload, localPayload, remotePayload) {
const conflicts = [];
const base = basePayload?.storage || {};
@@ -2650,6 +2666,12 @@ window.SUN_LEGACY_CATALOG_V175=[];
const keys = new Set([...Object.keys(base), ...Object.keys(local), ...Object.keys(remote)]);
for (const key of keys) {
const b = base[key], l = local[key], r = remote[key];
+ if(!shouldSyncKey(key)||(workspace&&!canWriteStorageKey(key))){
+ // Keep legacy remote-only preferences byte-for-byte, never upload local
+ // notification flags and never ask staff to delete an owner's settings.
+ if(r!==undefined)merged[key]=clone(r);
+ continue;
+ }
if (l === undefined && r === undefined) continue;
if (l === undefined) {
if (b !== undefined && !equal(r,b)) conflicts.push(key);
@@ -2663,6 +2685,16 @@ window.SUN_LEGACY_CATALOG_V175=[];
}
merged[key] = mergeNode(b, l, r, conflicts, key);
}
+ if(workspace&&(remote.sunOrders||merged.sunOrders)){
+ const existing=remote.sunOrders?.v,proposed=merged.sunOrders?.v;
+ if(idArray(existing)&&(!proposed||idArray(proposed))){
+ const byId=new Map(existing.map(o=>[String(o.id),o]));
+ const rows=(proposed||[]).filter(o=>byId.has(String(o.id))||hasPermission('orders.create'))
+ .map(o=>byId.has(String(o.id))&&!hasPermission('orders.edit')?clone(byId.get(String(o.id))):o);
+ if(!hasPermission('orders.delete')){const ids=new Set(rows.map(o=>String(o.id)));for(const o of existing)if(!ids.has(String(o.id)))rows.push(clone(o));}
+ merged.sunOrders={...(merged.sunOrders||remote.sunOrders),v:rows};
+ }
+ }
return {payload:{format:'sun-cloud-v2',version:2,storage:merged}, conflicts:[...new Set(conflicts)]};
}
@@ -2955,7 +2987,7 @@ window.SUN_LEGACY_CATALOG_V175=[];
if(!current())return;
if (!remote || payloadEmpty(remote.payload)) {
if (!canWrite()) throw new Error('В облаке нет базы, а у пользователя нет прав на её создание.');
- const row = await upsertPayload(localPayload, remote?.revision ?? null);
+ const row = await upsertPayload(mergePayloads(null,localPayload,remote?.payload).payload, remote?.revision ?? null);
if(!current())return;
await setBaseline(row); if(!current())return; dirty=false; config.lastSync=row.updated_at||new Date().toISOString();saveConfig();
setStatus('ready','Синхронизировано.'); if(!quiet)toast('Синхронизация завершена.','success'); return;
@@ -2970,8 +3002,8 @@ window.SUN_LEGACY_CATALOG_V175=[];
const merged = mergePayloads(baseline.payload, localPayload, remote.payload);
let finalRow = remote;
- const changedVsRemote = !equal(merged.payload, remote.payload);
- const changedVsLocal = !equal(merged.payload, localPayload);
+ const changedVsRemote = !equalSyncPayload(merged.payload, remote.payload);
+ const changedVsLocal = !equalSyncPayload(merged.payload, localPayload);
if (changedVsRemote) {
if (!canWrite()) {
diff --git a/public/core/account-center-v1780.js b/public/core/account-center-v1780.js
index 070e41e..40ba540 100644
--- a/public/core/account-center-v1780.js
+++ b/public/core/account-center-v1780.js
@@ -1,7 +1,7 @@
(()=>{
'use strict';
if(window.CateriumAccountCenterV1780)return;
-const VERSION='17.8.0-account-center-v3';
+const VERSION='17.8.0-employee-session-20260920';
const $=(s,r=document)=>r.querySelector(s);
const qa=(s,r=document)=>[...r.querySelectorAll(s)];
const esc=v=>window.SunSafe?.escapeHTML?window.SunSafe.escapeHTML(String(v??'')):String(v??'');
@@ -14,6 +14,10 @@ const ROLE_LABELS={admin:'Владелец',manager:'Менеджер',kitchen:'
let snapshot=null;
let modal=null;
let lastUserId='';
+let snapshotScope='',loadSequence=0,openSequence=0;
+const accountScope=()=>JSON.stringify([session()?.user?.id||'',workspace()?.id||'']);
+const profileIcon='';
+const logoutIcon='';
function installStyle(){
if($('#caterium-account-center-style'))return;
@@ -21,8 +25,9 @@ function installStyle(){
#cateriumAccountCenter{position:fixed;inset:0;z-index:21000;display:none;place-items:center;padding:22px;background:#102c3d99;backdrop-filter:blur(5px)}
#cateriumAccountCenter.on{display:grid}
#cateriumAccountCenter .cac-shell{width:min(980px,100%);max-height:min(860px,92vh);overflow:auto;background:#f6f2ea;border-radius:22px;box-shadow:0 28px 90px #0004;color:#2f2b25}
- #cateriumAccountCenter .cac-head{display:flex;justify-content:space-between;gap:16px;align-items:flex-start;padding:28px 30px 20px;border-bottom:1px solid #d9d1c5}
+ #cateriumAccountCenter .cac-head{position:sticky;top:0;z-index:2;background:#f6f2ea;display:flex;justify-content:space-between;gap:16px;align-items:flex-start;padding:28px 30px 20px;border-bottom:1px solid #d9d1c5}
#cateriumAccountCenter h2{margin:0;font:500 38px/1.04 Georgia,'Times New Roman',serif;letter-spacing:-.03em;color:#27231f}
+ #cateriumAccountCenter .cac-head-actions{display:flex;align-items:center;gap:12px;flex-shrink:0}
#cateriumAccountCenter .cac-sub{margin-top:7px;color:#8a8379;font-size:14px}
#cateriumAccountCenter .cac-close{border:0;background:transparent;font-size:31px;color:#5c564d;padding:0 4px}
#cateriumAccountCenter .cac-body{padding:24px 30px 30px}
@@ -45,31 +50,44 @@ function installStyle(){
#cateriumAccountButton{display:flex!important}
#sun-cloud-users-modal [data-caterium-owner-row="1"]{background:#fff9e8;border:1px solid #ead39a;border-radius:10px;padding:10px}
#sun-cloud-users-modal .caterium-owner-chip{display:inline-flex;align-items:center;border-radius:999px;background:#f4dfad;color:#6d5118;padding:5px 9px;font-size:11px;font-weight:900}
+ #cateriumMobileAccountActions{display:none}
+ #cateriumMobileAccountActions button svg,#cateriumAccountButton svg{width:20px;height:20px;flex-shrink:0}
+ @media(max-width:900px){
+ body.sun-enterprise-sidebar header #cateriumMobileAccountActions{display:flex!important;width:100%;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap;margin:6px 0 0}
+ #cateriumMobileAccountActions button{display:flex!important;align-items:center;justify-content:center;gap:7px;min-height:44px;border:1px solid #b8b4a9;border-radius:10px;padding:8px 12px;background:#faf7ef;color:#333c30;font:700 13px Arial;touch-action:manipulation}
+ #cateriumAccountCenter h2{font-size:27px}
+ #cateriumAccountCenter .cac-head-actions{gap:8px}
+ #cacHeaderLogout{min-height:44px;font-size:12px;padding:8px!important;white-space:nowrap}
+ }
+ @media print{#cateriumMobileAccountActions,#cateriumAccountCenter{display:none!important}}
@media(max-width:720px){#cateriumAccountCenter{padding:0;place-items:stretch}#cateriumAccountCenter .cac-shell{width:100%;max-height:none;height:100dvh;border-radius:0}#cateriumAccountCenter .cac-grid{grid-template-columns:1fr}#cateriumAccountCenter .cac-card.wide{grid-column:auto}#cateriumAccountCenter .cac-head,#cateriumAccountCenter .cac-body{padding-left:18px;padding-right:18px}}
`;document.head.appendChild(s);
}
function ensureModal(){
if(modal)return modal;
- modal=document.createElement('div');modal.id='cateriumAccountCenter';modal.innerHTML='