'use client'; import { SettingItem, SettingsSection, ToggleSwitch } from '@/components/settings/settings-section'; import { InheritNote } from '../org-page'; import type { OrgPolicyDraft } from '../use-org-policy-draft'; const DOCS_URL = 'https://bulwarkmail.org/docs/guides/organisation/offline-mode'; interface CapField { key: 'offlineMaxDays' | 'offlineMaxAttachmentMb' | 'offlineMaxTotalMb'; label: string; description: string; unit: string; max: number; } const CAP_FIELDS: CapField[] = [ { key: 'offlineMaxDays', label: 'Keep mail for at most', description: 'Mail older than this leaves the device at its next policy fetch, and calendar events that ended before it too. People can still choose less.', unit: 'days', max: 3650, }, { key: 'offlineMaxAttachmentMb', label: 'Largest attachment or file kept', description: 'Anything bigger leaves the device and opens from the server when there is a connection. This also holds for files someone chose to keep offline.', unit: 'MB', max: 1024 * 1024, }, { key: 'offlineMaxTotalMb', label: 'All attachments and files, per account', description: 'The oldest go first when a device is over this. Message text isn’t counted: limit that with the days above.', unit: 'MB', max: 1024 * 1024, }, ]; const describeCap = (value: number, unit: string) => (value > 0 ? `${value} ${unit}` : 'no limit'); /** * Organisation > Devices > Offline mode (#523): whether people may keep mail * on their devices, and how much (policy.json `organisation.devices`). These * win over what a user picks and over the desktop app's default. Nothing on * the server can check them, since the mail is on the device: the app holds * itself to them, which the text says. */ export function OfflineSection({ draft }: { draft: OrgPolicyDraft }) { const forbid = draft.orgValue('devices', 'noOffline'); const noAttachments = draft.orgValue('devices', 'noOfflineAttachments'); const wipeOnRefused = draft.orgValue('devices', 'wipeOnForcedSignOut'); return (
draft.setOrgValue('devices', 'noOffline', v)} testId="org-devices-noOffline" /> draft.clearOrgValue('devices', 'noOffline')} />
{CAP_FIELDS.map((field) => { const { value, own, everyone } = draft.orgValue('devices', field.key); return (
0 ? value : ''} onChange={(e) => { const raw = e.target.value.trim(); const parsed = raw === '' ? 0 : Math.round(Number(raw)); if (!Number.isFinite(parsed)) return; draft.setOrgValue('devices', field.key, Math.min(field.max, Math.max(0, parsed))); }} className="h-9 w-28 rounded-md border border-input bg-background px-3 text-sm text-foreground placeholder:text-muted-foreground focus:outline-hidden" /> {field.unit}
draft.clearOrgValue('devices', field.key)} />
); })}
draft.setOrgValue('devices', 'noOfflineAttachments', v)} testId="org-devices-noOfflineAttachments" /> draft.clearOrgValue('devices', 'noOfflineAttachments')} />
draft.setOrgValue('devices', 'wipeOnForcedSignOut', v)} testId="org-devices-wipeOnForcedSignOut" /> draft.clearOrgValue('devices', 'wipeOnForcedSignOut')} />

What a device keeps: mail in every folder from the window the person picks (30 days, 90 days, a year or everything), with calendar, contacts and files, encrypted on the device. In a browser the key sits in the same profile, so this doesn't stop someone who copies its files: use full-disk encryption. In the desktop app the key also takes that person's login on that computer. Neither stops someone using the unlocked browser or app. An app lock adds a PIN, which someone with a copy can guess at their own speed, so ask for a long one. The limits above can only lower what people pick: where several scopes apply, the lowest value counts, so a scope can't raise what another one lowered. The app holds itself to them when it fetches the policy; the server can't check what is on a device. A sign-out someone asks for deletes everything. One that nobody asked for hides it, as the switch above says. With several accounts signed in, each account follows its own settings. Bulwark Lite reads the same policy but has no offline mode.{' '} How offline mode works

); }