diff --git a/e2e/a11y.spec.ts-snapshots/settings-nav.aria.yml b/e2e/a11y.spec.ts-snapshots/settings-nav.aria.yml index e5085f090..7be39b2db 100644 --- a/e2e/a11y.spec.ts-snapshots/settings-nav.aria.yml +++ b/e2e/a11y.spec.ts-snapshots/settings-nav.aria.yml @@ -1,22 +1,31 @@ - navigation "Settings sections": - - button "General" [pressed]: - - img - - paragraph: General - - button "Account": + - button "Account" [pressed]: - img - paragraph: Account + - button "Appearance": + - img + - paragraph: Appearance + - button "Messages": + - img + - paragraph: Messages - button "Notifications": - img - paragraph: Notifications - - button "Devices": + - button "Calls & audio": - img - - paragraph: Devices - - button "Emojis & Stickers": + - paragraph: Calls & audio + - button "Privacy & security": - img - - paragraph: Emojis & Stickers - - button "Developer Tools": + - paragraph: Privacy & security + - button "Data & storage": - img - - paragraph: Developer Tools + - paragraph: Data & storage + - button "Emojis & stickers": + - img + - paragraph: Emojis & stickers + - button "Advanced": + - img + - paragraph: Advanced - button "About": - img - paragraph: About diff --git a/e2e/key-upload-recovery.spec.ts b/e2e/key-upload-recovery.spec.ts index e2a646eaa..bea1c6d62 100644 --- a/e2e/key-upload-recovery.spec.ts +++ b/e2e/key-upload-recovery.spec.ts @@ -31,7 +31,7 @@ test.describe('key upload recovery (#201)', () => { expect(rejected).toBeGreaterThanOrEqual(3); await banner.getByRole('button', { name: 'Details' }).click(); - await expect(banner).toContainText('Settings → Devices'); + await expect(banner).toContainText('Settings → Privacy & security'); await expect(banner).toContainText('already exists'); await banner.getByRole('button', { name: /Fix: sign out/ }).click(); diff --git a/e2e/security-nudge.spec.ts b/e2e/security-nudge.spec.ts index d90d05d3c..a19c1df9b 100644 --- a/e2e/security-nudge.spec.ts +++ b/e2e/security-nudge.spec.ts @@ -75,7 +75,9 @@ test.describe('security nudge (#110, #123)', () => { await expect(strip(page)).toHaveCount(0); }); - test('no key backup: "Set up key backup" opens Settings → Devices', async ({ page }) => { + test('no key backup: "Set up key backup" opens Settings → Privacy & security', async ({ + page, + }) => { test.setTimeout(150_000); await loginUI(page, user); await pastGrace(page); diff --git a/e2e/your-data.spec.ts b/e2e/your-data.spec.ts index 379ae5e37..76f12c18b 100644 --- a/e2e/your-data.spec.ts +++ b/e2e/your-data.spec.ts @@ -39,7 +39,7 @@ test.describe('your data (#114)', () => { .catch(() => undefined); const openGeneral = async () => { await page.getByRole('button', { name: 'User Settings' }).click(); - await page.getByRole('dialog').getByText('General', { exact: true }).first().click(); + await page.getByRole('dialog').getByText('Data & storage', { exact: true }).first().click(); await page .getByRole('dialog') .getByText('Your data', { exact: true }) diff --git a/src/app/components/CloseBehaviorPrompt.tsx b/src/app/components/CloseBehaviorPrompt.tsx index 6983b2e06..977ce1600 100644 --- a/src/app/components/CloseBehaviorPrompt.tsx +++ b/src/app/components/CloseBehaviorPrompt.tsx @@ -19,7 +19,7 @@ import { useModalStyle } from '../hooks/useModalStyle'; * cinny-desktop #5: the first time the window is closed, ask whether Lotus Chat * should keep running in the tray (calls, messages, notifications) or quit — * and, in the same moment, whether it should start when you sign in. Asked - * once; changeable in Settings → General. The native side sends + * once; changeable in Settings → Desktop. The native side sends * `lotus-close-requested` only while the choice is still "ask", and never * during a call (a call always goes to the tray). */ @@ -149,7 +149,7 @@ export function CloseBehaviorPrompt() { )} - You can change this in Settings → General. + You can change this in Settings → Desktop. diff --git a/src/app/features/common-settings/general/RoomRetention.tsx b/src/app/features/common-settings/general/RoomRetention.tsx index ee8ee5870..cf573ca8a 100644 --- a/src/app/features/common-settings/general/RoomRetention.tsx +++ b/src/app/features/common-settings/general/RoomRetention.tsx @@ -48,7 +48,7 @@ export function RoomRetention({ permissions }: RoomRetentionProps) { > {RETENTION_PRESETS.map((preset) => { diff --git a/src/app/features/settings/Settings.tsx b/src/app/features/settings/Settings.tsx index 751e54e0e..07b90d317 100644 --- a/src/app/features/settings/Settings.tsx +++ b/src/app/features/settings/Settings.tsx @@ -15,7 +15,16 @@ import { Text, } from 'folds'; import FocusTrap from 'focus-trap-react'; -import { General } from './general'; +import { + AdvancedDisplayOptions, + AppearancePage, + CallsPage, + DataStoragePage, + DesktopPage, + MessagesPage, + Privacy, +} from './general'; +import { isTauri as isTauriEnv } from '../../hooks/useTauri'; import { PageNav, PageNavContent, PageNavHeader, PageRoot } from '../../components/page'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; import { Account } from './account'; @@ -45,43 +54,23 @@ type SettingsMenuItem = { const useSettingsMenuItems = (): SettingsMenuItem[] => useMemo( - () => [ - { - page: SettingsPages.GeneralPage, - name: 'General', - icon: Icons.Setting, - }, - { - page: SettingsPages.AccountPage, - name: 'Account', - icon: Icons.User, - }, - { - page: SettingsPages.NotificationPage, - name: 'Notifications', - icon: Icons.Bell, - }, - { - page: SettingsPages.DevicesPage, - name: 'Devices', - icon: Icons.Monitor, - }, - { - page: SettingsPages.EmojisStickersPage, - name: 'Emojis & Stickers', - icon: Icons.Smile, - }, - { - page: SettingsPages.DeveloperToolsPage, - name: 'Developer Tools', - icon: Icons.Terminal, - }, - { - page: SettingsPages.AboutPage, - name: 'About', - icon: Icons.Info, - }, - ], + () => + [ + { page: SettingsPages.AccountPage, name: 'Account', icon: Icons.User }, + { page: SettingsPages.AppearancePage, name: 'Appearance', icon: Icons.Sun }, + { page: SettingsPages.MessagesPage, name: 'Messages', icon: Icons.Message }, + { page: SettingsPages.NotificationPage, name: 'Notifications', icon: Icons.Bell }, + { page: SettingsPages.CallsPage, name: 'Calls & audio', icon: Icons.Phone }, + { page: SettingsPages.DevicesPage, name: 'Privacy & security', icon: Icons.ShieldUser }, + { page: SettingsPages.DataStoragePage, name: 'Data & storage', icon: Icons.Download }, + // [Gitea #459] Desktop-only options: nothing to show in a browser. + ...(isTauriEnv() + ? [{ page: SettingsPages.DesktopPage, name: 'Desktop', icon: Icons.Monitor }] + : []), + { page: SettingsPages.EmojisStickersPage, name: 'Emojis & stickers', icon: Icons.Smile }, + { page: SettingsPages.DeveloperToolsPage, name: 'Advanced', icon: Icons.Terminal }, + { page: SettingsPages.AboutPage, name: 'About', icon: Icons.Info }, + ] as SettingsMenuItem[], [], ); @@ -102,7 +91,7 @@ export function Settings({ initialPage, requestClose }: SettingsProps) { const screenSize = useScreenSizeContext(); const [activePage, setActivePage] = useState(() => { if (initialPage) return initialPage; - return screenSize === ScreenSize.Mobile ? undefined : SettingsPages.GeneralPage; + return screenSize === ScreenSize.Mobile ? undefined : SettingsPages.AccountPage; }); const menuItems = useSettingsMenuItems(); @@ -207,8 +196,20 @@ export function Settings({ initialPage, requestClose }: SettingsProps) { ) } > - {activePage === SettingsPages.GeneralPage && ( - + {activePage === SettingsPages.AppearancePage && ( + + )} + {activePage === SettingsPages.MessagesPage && ( + + )} + {activePage === SettingsPages.CallsPage && ( + + )} + {activePage === SettingsPages.DataStoragePage && ( + + )} + {activePage === SettingsPages.DesktopPage && ( + )} {activePage === SettingsPages.AccountPage && ( @@ -217,13 +218,21 @@ export function Settings({ initialPage, requestClose }: SettingsProps) { )} {activePage === SettingsPages.DevicesPage && ( - + } + requestClose={handlePageRequestClose} + /> )} {activePage === SettingsPages.EmojisStickersPage && ( )} {activePage === SettingsPages.DeveloperToolsPage && ( - + } + requestClose={handlePageRequestClose} + /> )} {activePage === SettingsPages.AboutPage && } diff --git a/src/app/features/settings/account/Profile.tsx b/src/app/features/settings/account/Profile.tsx index 44f1036f6..50270db27 100644 --- a/src/app/features/settings/account/Profile.tsx +++ b/src/app/features/settings/account/Profile.tsx @@ -313,7 +313,7 @@ function ProfileDisplayName({ profile, userId }: ProfileProps) { - Display Name + Display name } > @@ -576,7 +576,7 @@ function ProfileStatus() { - Status Message + Status message } description={ diff --git a/src/app/features/settings/account/ProfileDecoration.tsx b/src/app/features/settings/account/ProfileDecoration.tsx index 85095ec59..fde1f8497 100644 --- a/src/app/features/settings/account/ProfileDecoration.tsx +++ b/src/app/features/settings/account/ProfileDecoration.tsx @@ -102,6 +102,8 @@ export function ProfileDecoration() { const [current, setCurrent] = useState(null); const [selected, setSelected] = useState(null); + // [Gitea #460] The catalog stays closed until "Change"; it was always open. + const [browsing, setBrowsing] = useState(false); // Distinguish "confirmed no decoration" from "failed to load": a fetch // failure must not be shown as (and saved over) "None". const [loadError, setLoadError] = useState(false); @@ -199,7 +201,7 @@ export function ProfileDecoration() { - Avatar Decoration + Avatar decoration } description={ @@ -282,6 +284,20 @@ export function ProfileDecoration() { )} + {!loadError && ( + + )} {hasChanges && !loadError && ( + + } + /> + {open && ( + + {children} + + )} + + ); +} + function SeasonalBgGrid({ value, onChange, @@ -2663,11 +2726,7 @@ function SelectMessageSpacing() { ); } -function Messages() { - const [legacyUsernameColor, setLegacyUsernameColor] = useSetting( - settingsAtom, - 'legacyUsernameColor', - ); +export function Messages() { const [hideMembershipEvents, setHideMembershipEvents] = useSetting( settingsAtom, 'hideMembershipEvents', @@ -2680,7 +2739,6 @@ function Messages() { const [urlPreview, setUrlPreview] = useSetting(settingsAtom, 'urlPreview'); const [encUrlPreview, setEncUrlPreview] = useSetting(settingsAtom, 'encUrlPreview'); const [inlineMediaEmbeds, setInlineMediaEmbeds] = useSetting(settingsAtom, 'inlineMediaEmbeds'); - const [showHiddenEvents, setShowHiddenEvents] = useSetting(settingsAtom, 'showHiddenEvents'); const [enforceRetentionLocally, setEnforceRetentionLocally] = useSetting( settingsAtom, 'enforceRetentionLocally', @@ -2707,18 +2765,6 @@ function Messages() { } /> - - - } - /> - )} - - - } - /> - @@ -2952,7 +2990,7 @@ function DesktopSettings() { ); } -function AppUpdates() { +export function AppUpdates() { const { isTauri, status, check, install, installKind } = useTauriUpdater(); if (!isTauri) return null; const packageHelp = packageUpdateHelp(installKind); @@ -3031,14 +3069,60 @@ function AppUpdates() { type GeneralProps = { requestClose: () => void; }; -export function General({ requestClose }: GeneralProps) { + +/** [Gitea #459] Display options for people who want to see the plumbing. */ +export function AdvancedDisplayOptions() { + const [legacyUsernameColor, setLegacyUsernameColor] = useSetting( + settingsAtom, + 'legacyUsernameColor', + ); + const [showHiddenEvents, setShowHiddenEvents] = useSetting(settingsAtom, 'showHiddenEvents'); + + return ( + + Display + + + } + /> + + + + } + /> + + + ); +} + +/** + * [Gitea #459] General used to hold 76 of the ~110 settings (11 screens). Its + * sections now live on pages named for what people come to do; Privacy goes + * to Privacy & security (devices/Devices.tsx) and the display plumbing to + * Advanced (developer-tools). + */ +function SectionPage({ + title, + requestClose, + children, +}: GeneralProps & { title: string; children: ReactNode }) { return ( - General + {title} @@ -3052,17 +3136,7 @@ export function General({ requestClose }: GeneralProps) { - - - - - - - - - - - + {children} @@ -3070,3 +3144,49 @@ export function General({ requestClose }: GeneralProps) { ); } + +export function AppearancePage({ requestClose }: GeneralProps) { + return ( + + + + ); +} + +export function MessagesPage({ requestClose }: GeneralProps) { + return ( + + + + + + ); +} + +export function CallsPage({ requestClose }: GeneralProps) { + return ( + + + + ); +} + +export function DataStoragePage({ requestClose }: GeneralProps) { + return ( + + + + + + ); +} + +/** Desktop app only (the menu hides it elsewhere). */ +export function DesktopPage({ requestClose }: GeneralProps) { + return ( + + + + + ); +} diff --git a/src/app/features/settings/notifications/PushRuleEditor.tsx b/src/app/features/settings/notifications/PushRuleEditor.tsx index 6e92096ab..628b3e56f 100644 --- a/src/app/features/settings/notifications/PushRuleEditor.tsx +++ b/src/app/features/settings/notifications/PushRuleEditor.tsx @@ -334,6 +334,11 @@ export function PushRuleEditor() { return ( Advanced push rules + {/* [Gitea #459] Matrix terms kept, with one line on what they are. */} + + The raw Matrix notification rules behind the options above, checked in this order: override, + room, sender, then underride. You rarely need to change these. + { - if (viaPackage) requestSettings('general'); + if (viaPackage) requestSettings('desktop'); else installFromToast(); }, sticky: true, diff --git a/src/app/pages/client/ClientRoot.tsx b/src/app/pages/client/ClientRoot.tsx index 1c2b7c379..fd444594a 100644 --- a/src/app/pages/client/ClientRoot.tsx +++ b/src/app/pages/client/ClientRoot.tsx @@ -61,7 +61,7 @@ import { AutoDiscovery } from './AutoDiscovery'; // Capture-only E2EE diagnostics ring buffer (KE-1→4 signatures) — installed at // module load so it sees crypto warnings from the very first sync. Idempotent; -// report download lives in Settings → Developer Tools → Crypto Diagnostics. +// report download lives in Settings → Advanced → Crypto diagnostics. installCryptoDiagLog(); function ClientRootLoading() { diff --git a/src/app/pages/client/KeyUploadBanner.tsx b/src/app/pages/client/KeyUploadBanner.tsx index 7fd2e5d98..7f71d4708 100644 --- a/src/app/pages/client/KeyUploadBanner.tsx +++ b/src/app/pages/client/KeyUploadBanner.tsx @@ -72,8 +72,8 @@ export function KeyUploadBanner() { rejects every new upload and other people can't start fresh encrypted sessions with you. This usually happens after the browser cleared this site's storage while you stayed signed in. Before signing out, make sure this device is verified or - key backup is on (Settings → Devices), so your message history comes back when you - sign in again. + key backup is on (Settings → Privacy & security), so your message history comes back + when you sign in again. {lastError && ( diff --git a/src/app/pages/client/sidebar/SettingsTab.tsx b/src/app/pages/client/sidebar/SettingsTab.tsx index 081d87c95..f12457db3 100644 --- a/src/app/pages/client/sidebar/SettingsTab.tsx +++ b/src/app/pages/client/sidebar/SettingsTab.tsx @@ -180,7 +180,10 @@ export function SettingsTab() { useEffect(() => { if (!settingsRequest) return; const page = { - general: SettingsPages.GeneralPage, + // [Gitea #459] General is gone; its old deep links land on Appearance. + general: SettingsPages.AppearancePage, + appearance: SettingsPages.AppearancePage, + desktop: SettingsPages.DesktopPage, account: SettingsPages.AccountPage, notifications: SettingsPages.NotificationPage, devices: SettingsPages.DevicesPage, diff --git a/src/app/state/settingsRequest.ts b/src/app/state/settingsRequest.ts index 5e2ce23b6..3c68adced 100644 --- a/src/app/state/settingsRequest.ts +++ b/src/app/state/settingsRequest.ts @@ -1,6 +1,12 @@ import { atom } from 'jotai'; -export type SettingsRequestPage = 'general' | 'account' | 'notifications' | 'devices'; +export type SettingsRequestPage = + | 'general' + | 'account' + | 'notifications' + | 'devices' + | 'desktop' + | 'appearance'; /** * Ask the sidebar to open User Settings at a page from anywhere in the app diff --git a/src/app/utils/securityNudge.ts b/src/app/utils/securityNudge.ts index 38b82182a..04a2c995d 100644 --- a/src/app/utils/securityNudge.ts +++ b/src/app/utils/securityNudge.ts @@ -10,7 +10,7 @@ * cross-signing: the setup flow does both). * * - stale-sessions (#113, lowest priority): sessions of this account not used - * for 90+ days; Review opens Settings → Devices with them ticked to sign out. + * for 90+ days; Review opens Settings → Privacy & security with them ticked to sign out. * * Anything not yet known (crypto still loading) → no nudge. Timing: not in * the first 24 h on a device (onboarding), "Not now" snoozes 7 days, three