Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/studio/src/ipc-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,13 +215,15 @@ export {

export {
getColorScheme,
getDensity,
getInstalledAppsAndTerminals,
getUserEditor,
getUserLocale,
getUserTerminal,
getWapuuScore,
previewColorScheme,
saveColorScheme,
saveDensity,
saveUserEditor,
saveUserLocale,
saveUserTerminal,
Expand Down
9 changes: 9 additions & 0 deletions apps/studio/src/modules/user-settings/lib/ipc-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,15 @@ export async function getColorScheme(): Promise< 'system' | 'light' | 'dark' > {
return colorScheme;
}

export async function saveDensity( event: IpcMainInvokeEvent, density: 'compact' | 'comfortable' ) {
await updateAppdata( { density } );
}

export async function getDensity(): Promise< 'compact' | 'comfortable' > {
const userData = await loadUserData();
return userData.density ?? 'compact';
}

export async function saveWapuuScore( _event: IpcMainInvokeEvent, score: number ): Promise< void > {
if ( ! Number.isFinite( score ) || score < 0 || score > 100_000 ) {
return;
Expand Down
2 changes: 2 additions & 0 deletions apps/studio/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ const api: IpcApi = {
previewColorScheme: ( colorScheme ) => ipcRendererInvoke( 'previewColorScheme', colorScheme ),
saveColorScheme: ( colorScheme ) => ipcRendererInvoke( 'saveColorScheme', colorScheme ),
getColorScheme: () => ipcRendererInvoke( 'getColorScheme' ),
saveDensity: ( density ) => ipcRendererInvoke( 'saveDensity', density ),
getDensity: () => ipcRendererInvoke( 'getDensity' ),
saveWapuuScore: ( score ) => ipcRendererInvoke( 'saveWapuuScore', score ),
getWapuuScore: () => ipcRendererInvoke( 'getWapuuScore' ),
getUserEditor: () => ipcRendererInvoke( 'getUserEditor' ),
Expand Down
1 change: 1 addition & 0 deletions apps/studio/src/storage/storage-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface UserData {
preferredTerminal?: SupportedTerminal;
preferredEditor?: SupportedEditor;
colorScheme?: 'system' | 'light' | 'dark';
density?: 'compact' | 'comfortable';
betaFeatures?: BetaFeatures;
stopSitesOnQuit?: boolean;
defaultSiteDirectory?: string;
Expand Down
1 change: 1 addition & 0 deletions apps/studio/src/storage/user-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ type UserDataSafeKeys =
| 'preferredEditor'
| 'betaFeatures'
| 'colorScheme'
| 'density'
| 'defaultSiteDirectory'
| 'cliAutoInstalled'
| 'cliUserUninstalled'
Expand Down
15 changes: 15 additions & 0 deletions apps/ui/src/app/app-providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { QueryClientProvider } from '@tanstack/react-query';
import { defaultI18n } from '@wordpress/i18n';
import { I18nProvider } from '@wordpress/react-i18n';
import { privateApis } from '@wordpress/theme';
import { useEffect } from 'react';
import { ConnectorProvider, queryClient } from '@/data/core';
import { AgentRunProvider } from '@/data/queries/use-agent-run';
import { useSyncSessionsWithEvents } from '@/data/queries/use-sessions';
import { useSyncSitesWithEvents } from '@/data/queries/use-sites';
import { useUserPreferences } from '@/data/queries/use-user-preferences';
import { usePrefersColorScheme } from '@/hooks/use-prefers-color-scheme';
import { useSyncConnectSiteListener } from '@/hooks/use-sync-connect-site-listener';
import { unlock } from '@/lock-unlock';
Expand All @@ -25,6 +27,18 @@ function SiteEventsBridge() {
return null;
}

// Mirrors the saved density preference onto `<html>` so the token overrides
// in `index.css` apply everywhere, including portals mounted on `body` that
// escape the root ThemeProvider's wrapper.
function DensityBridge() {
const { data: preferences } = useUserPreferences();
const density = preferences?.density ?? 'compact';
useEffect( () => {
document.documentElement.dataset.studioDensity = density;
}, [ density ] );
return null;
}

export function AppProviders( { children, connector }: AppProvidersProps ) {
const colorScheme = usePrefersColorScheme();
const themeColor = colorScheme === 'dark' ? { bg: '#1e1e1e' } : undefined;
Expand All @@ -34,6 +48,7 @@ export function AppProviders( { children, connector }: AppProvidersProps ) {
<QueryClientProvider client={ queryClient }>
<AgentRunProvider>
<SiteEventsBridge />
<DensityBridge />
<I18nProvider i18n={ defaultI18n }>
<ThemeProvider isRoot color={ themeColor } density="compact">
{ children }
Expand Down
21 changes: 20 additions & 1 deletion apps/ui/src/components/settings-view/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useSidebarCollapsed } from '@/hooks/use-sidebar-collapsed';
import styles from './style.module.css';
import type {
ColorScheme,
Density,
InstalledApps,
SupportedEditor,
SupportedLocale,
Expand All @@ -44,6 +45,7 @@ interface FormData {
editor: SupportedEditor | typeof UNSET;
terminal: SupportedTerminal | typeof UNSET;
colorScheme: ColorScheme;
density: Density;
locale: SupportedLocale;
}

Expand All @@ -59,6 +61,7 @@ function toFormData( prefs: UserPreferences ): FormData {
editor: prefs.editor ?? UNSET,
terminal: prefs.terminal ?? UNSET,
colorScheme: prefs.colorScheme,
density: prefs.density,
locale: resolveFormLocale( prefs.locale ),
};
}
Expand All @@ -73,6 +76,7 @@ function diffFromSaved(
if ( nextEditor !== saved.editor ) patch.editor = nextEditor;
if ( nextTerminal !== saved.terminal ) patch.terminal = nextTerminal;
if ( next.colorScheme !== saved.colorScheme ) patch.colorScheme = next.colorScheme;
if ( next.density !== saved.density ) patch.density = next.density;
if ( next.locale !== resolveFormLocale( saved.locale ) ) patch.locale = next.locale;
return patch;
}
Expand Down Expand Up @@ -103,6 +107,11 @@ const COLOR_SCHEME_ELEMENTS: { value: ColorScheme; label: string }[] = [
{ value: 'dark', label: __( 'Dark' ) },
];

const DENSITY_ELEMENTS: { value: Density; label: string }[] = [
{ value: 'compact', label: __( 'Compact' ) },
{ value: 'comfortable', label: __( 'Comfortable' ) },
];

const LOCALE_ELEMENTS: { value: SupportedLocale; label: string }[] = Object.entries(
supportedLocaleNames
).map( ( [ value, label ] ) => ( { value: value as SupportedLocale, label } ) );
Expand Down Expand Up @@ -264,6 +273,12 @@ export function SettingsView( {
label: __( 'Appearance' ),
elements: COLOR_SCHEME_ELEMENTS,
},
{
id: 'density',
type: 'text',
label: __( 'Density' ),
elements: DENSITY_ELEMENTS,
},
{
id: 'locale',
type: 'text',
Expand All @@ -283,7 +298,11 @@ export function SettingsView( {
layout: { type: 'row' },
children: [ 'editor', 'terminal' ],
},
'colorScheme',
{
id: 'appearance',
layout: { type: 'row' },
children: [ 'colorScheme', 'density' ],
},
'locale',
],
} ),
Expand Down
1 change: 1 addition & 0 deletions apps/ui/src/components/site-preview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ export function SitePreview( {
variant="minimal"
tone="neutral"
size="small"
className={ styles.refreshControl }
icon={ refreshIcon }
label={ __( 'Refresh' ) }
shortcut={ browserShortcuts.reload }
Expand Down
8 changes: 7 additions & 1 deletion apps/ui/src/components/site-preview/style.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
display: flex;
align-items: center;
gap: var(--wpds-dimension-padding-md);
padding: var(--wpds-dimension-padding-xs) var(--wpds-dimension-padding-lg);
padding: var(--wpds-dimension-padding-md) var(--wpds-dimension-padding-xl);
min-height: 32px;
flex-shrink: 0;
background-color: var(--wpds-color-bg-surface-neutral-strong, #fff);
Expand All @@ -32,6 +32,12 @@
flex-shrink: 0;
}

/* Back/forward read as one cluster; refresh is a different kind of action,
so set it slightly apart. */
.refreshControl {
margin-inline-start: var(--wpds-dimension-gap-xs);
}

.browserLocation {
display: flex;
flex: 1 1 auto;
Expand Down
10 changes: 8 additions & 2 deletions apps/ui/src/data/core/connectors/ipc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
AuthUser,
ColorScheme,
Connector,
Density,
DeskConfig,
DeskSettings,
ExtractedBlueprintBundle,
Expand Down Expand Up @@ -596,18 +597,20 @@ export function createIpcConnector(): Connector {
// per field; we fan out in parallel here so the UI can work with a
// single query/mutation pair.
async getUserPreferences(): Promise< UserPreferences > {
const [ editor, terminal, colorScheme, locale ] = ( await Promise.all( [
const [ editor, terminal, colorScheme, density, locale ] = ( await Promise.all( [
ipcApi.getUserEditor(),
ipcApi.getUserTerminal(),
ipcApi.getColorScheme(),
ipcApi.getDensity(),
ipcApi.getUserLocale(),
] ) ) as [
SupportedEditor | null,
SupportedTerminal | null,
ColorScheme,
Density,
string | undefined,
];
return { editor, terminal, colorScheme, locale };
return { editor, terminal, colorScheme, density, locale };
},

async setUserPreferences( partial ): Promise< void > {
Expand All @@ -621,6 +624,9 @@ export function createIpcConnector(): Connector {
if ( 'colorScheme' in partial && partial.colorScheme ) {
writes.push( ipcApi.saveColorScheme( partial.colorScheme ) );
}
if ( 'density' in partial && partial.density ) {
writes.push( ipcApi.saveDensity( partial.density ) );
}
if ( 'locale' in partial && partial.locale ) {
writes.push( ipcApi.saveUserLocale( partial.locale ) );
}
Expand Down
1 change: 1 addition & 0 deletions apps/ui/src/data/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type {
ColorScheme,
Connector,
CreateSiteParams,
Density,
DeskConfig,
DeskSettings,
DeskWidgetBase,
Expand Down
5 changes: 5 additions & 0 deletions apps/ui/src/data/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,15 @@ export interface Connector {

export type ColorScheme = 'system' | 'light' | 'dark';

// 'compact' is the design-system default the app ships with; 'comfortable'
// bumps the base UI font size for legibility (see `index.css`).
export type Density = 'compact' | 'comfortable';

export interface UserPreferences {
editor: SupportedEditor | null;
terminal: SupportedTerminal | null;
colorScheme: ColorScheme;
density: Density;
locale: string | undefined;
}

Expand Down
35 changes: 35 additions & 0 deletions apps/ui/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,30 @@
0 18px 44px rgba( 15, 23, 42, 0.1 );
}

/* Comfortable density (Settings → Preferences → Density): the design
system's default body size (md) is 13px, which can read small in the
desktop chrome. Bump it a step — md drives body text, site names, and
nav labels. The attribute lives on `<html>` (set by `DensityBridge` in
`app/app-providers.tsx`) so the override also reaches portals mounted
on `body`. Loaded after design-tokens.css so this wins. */
:root[data-studio-density='comfortable'] {
--wpds-typography-font-size-md: 14px;
}

/* Comfortable density: give `size="small"` buttons a step more height.
@wordpress/ui hardcodes the 24px small-button height (sizes don't track
density tokens), which reads cramped against the larger base font. The
package bakes its CSS-module classes as `<hash>__is-small`, so the suffix
is the only stable hook; being unlayered, this rule wins over the
package's `@layer wp-ui-components` declarations. Other components'
`__is-small` modifiers (e.g. Dialog.Popup) match too, but the
`--wp-ui-button-*` variables are inert outside Button — and they can't
leak to descendant buttons because every button re-declares them on
itself. */
:root[data-studio-density='comfortable'] [class*='__is-small'] {
--wp-ui-button-height: 28px;
}

body {
margin: 0;
padding: 0;
Expand Down Expand Up @@ -137,6 +161,17 @@ a:focus:not( :focus-visible ),
height: 16px;
}

/* Comfortable density (see the `data-studio-density` rules above): give
icons a step more presence alongside the larger type. `:where()` keeps
the specificity identical to the compact rule above — (0,2,1) — so this
only outranks it by source order, and component rules that deliberately
beat the base rule (e.g. the site-list status glyph's doubled-class
selector) keep their custom sizes in comfortable too. */
:root[data-studio-density='comfortable'] :where([data-wpds-density='compact'] [data-ui-mode='classic']) svg {
width: 18px;
height: 18px;
}

/* Desks uses each shape's indicator as the visible selection outline.
Hide tldraw's default selection box and handle glyphs while keeping the
underlying resize/rotate wrappers interactive. */
Expand Down
4 changes: 2 additions & 2 deletions apps/ui/src/lib/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ export const playIcon = (
export const refreshIcon = (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<path
d="M21.8883 13.5C21.1645 18.3113 17.013 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C16.1006 2 19.6248 4.46819 21.1679 8"
d="M20.9 13.35C20.248 17.68 16.512 21 12 21C7.029 21 3 16.971 3 12C3 7.029 7.029 3 12 3C15.691 3 18.862 5.221 20.251 8.4"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M17 8H21.4C21.7314 8 22 7.73137 22 7.4V3"
d="M16.5 8.4H20.46C20.758 8.4 21 8.158 21 7.86V3.9"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
Expand Down