PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.10
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.10
1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 All 34 releases
← All changes | includes/os-settings.php +270 -42 1.1.11.1.10 View file →
@@ -52,8 +52,28 @@
52 52 */
53 53 const OPENSTATION_OS_SETTINGS_DOCK_PLACEMENTS = array( 'bottom', 'left', 'right' );
54 54
55 55 /**
56 + * Valid dock-behavior IDs — mirrors the TS `DOCK_BEHAVIORS` constant.
57 + *
58 + * `static` keeps the dock always on screen (the default); `dynamic`
59 + * parks it off its edge behind a peek strip that reveals when the
60 + * pointer reaches that edge or something on it takes keyboard focus,
61 + * and releases the band it floats over from the work area.
62 + */
63 +const OPENSTATION_OS_SETTINGS_DOCK_BEHAVIORS = array( 'static', 'dynamic' );
64 +
65 +/**
66 + * `mobileLayout` — which experience the shell renders. `auto`
67 + * follows the viewport; the other two force it either way.
68 + * Mirrors `OsModePreference` in `src/mode/index.ts`.
69 + */
70 +const OPENSTATION_OS_SETTINGS_MOBILE_LAYOUTS = array( 'auto', 'desktop', 'mobile' );
71 +
72 +/** `mobileTabs` — at most this many ids pinned to the phone tab bar. */
73 +const OPENSTATION_OS_SETTINGS_MOBILE_TABS_MAX = 3;
74 +
75 +/**
56 76 * Playable range for the window-reveal duration override, in ms.
57 77 * Mirrors `MIN_REVEAL_DURATION_MS` / `MAX_REVEAL_DURATION_MS` in
58 78 * `src/reveals/registry.ts`.
59 79 *
@@ -73,8 +93,10 @@
73 93 */
74 94 function openstation_default_os_settings() {
75 95 return array(
76 96 'wallpaper' => 'galaxy',
97 + // Pulse, the brand's signature. Mirrors `DEFAULTS` in
98 + // `src/settings/constants.ts`.
77 99 'accent' => 'pulse',
78 100 // Only read when `accent` is `custom`. Seeded with Pulse so
79 101 // picking Custom before touching the wheel is a no-op rather
80 102 // than a jump to black. Mirrors `DEFAULTS` in
@@ -95,8 +117,14 @@
95 117 // dock, and the dock's "Exit OpenStation" tile is the way back
96 118 // to classic admin. `static` (vanilla behavior) and `dynamic`
97 119 // are one pick away in OpenStation Preferences → Appearance.
98 120 'adminBarMode' => 'hidden',
121 + // Always on screen. `dynamic` (auto-hide behind a peek strip)
122 + // is one pick away in OpenStation Preferences → Appearance.
123 + 'dockBehavior' => 'static',
124 + // The Split layout's sidebar answers for itself: a folded
125 + // sidebar over a static bottom dock is a valid desk.
126 + 'sideDockBehavior' => 'static',
99 127 // One dock holding every menu, with the system tiles grouped
100 128 // behind a hairline. `classic` (side bar for core menus + bottom
101 129 // dock for plugins) is the other option; it is no longer what a
102 130 // first-run desktop looks like.
@@ -184,8 +212,13 @@
184 212 // window (e.g. array( 'author', 'tags' )). Empty array means
185 213 // every column is visible. The sticky 'title' column is always
186 214 // shown — the UI prevents toggling it.
187 215 'nativePostsHiddenColumns' => array(),
216 + // Per-user list of column keys hidden in the native Pages
217 + // window (e.g. array( 'author', 'parent' )). Empty array means
218 + // every column is visible. The sticky 'title' column is always
219 + // shown — the UI prevents toggling it.
220 + 'nativePagesHiddenColumns' => array(),
188 221 // Per-user opt-IN for the native Pages window. Same posture as
189 222 // nativePostsEnabled — defaults OFF (Beta), users opt in to swap
190 223 // the classic `edit.php?post_type=page` iframe for the native UI.
191 224 'nativePagesEnabled' => false,
@@ -202,8 +235,17 @@
202 235 // Per-user opt-IN for the native Comments window. Defaults OFF
203 236 // (Beta); the server-side cap gate (`edit_posts`) means the
204 237 // toggle only matters for users who could see the Comments tile.
205 238 'nativeCommentsEnabled' => false,
239 + // Per-user opt-IN for Station Home, the native Dashboard
240 + // window. Defaults OFF: the ordinary `index.php` Dashboard
241 + // (including any custom dashboard a plugin builds there) opens
242 + // as a chromeless iframe until the user opts in via OS
243 + // Settings → Features → Beta features.
244 + 'stationHomeEnabled' => false,
245 + // Performance enhancements are enabled unless explicitly disabled.
246 + 'adminAssetCacheEnabled' => true,
247 + 'windowPrewarmEnabled' => true,
206 248 // When true, left-clicking the empty wallpaper triggers the
207 249 // "Show desktop" toggle (macOS-style) and the matching entry is
208 250 // hidden from the wallpaper context menu. When false (default),
209 251 // the entry stays in the menu and left clicks on the wallpaper
@@ -208,13 +250,20 @@
208 250 // hidden from the wallpaper context menu. When false (default),
209 251 // the entry stays in the menu and left clicks on the wallpaper
210 252 // do nothing. Per-user.
211 253 'showDesktopOnWallpaperClick' => false,
254 + // Whether the close-all-windows shortcut (Alt+Cmd/Ctrl+W) asks
255 + // before it closes. The dialog's "Don't ask again" checkbox is
256 + // what writes false; OpenStation Preferences -> Windows is what
257 + // turns it back on. Per-user.
258 + 'confirmCloseAllWindows' => true,
212 259 // Mio — a soft-body companion that floats over
213 260 // the wallpaper, settles onto nearby windows, and watches the
214 261 // pointer. Off by default; toggled from the wallpaper context
215 262 // menu. Per-user. See `docs/mio.md`.
216 263 'mioEnabled' => false,
264 + 'mioApiEnabled' => false,
265 + 'mioShowOnWallpaper' => true,
217 266 // The user's own Mio, as built in "Make it yours": partial
218 267 // appearance + silhouette overrides, both empty until they
219 268 // touch a control. Stored per user rather than per browser
220 269 // because it is a preference about the person — ten minutes
@@ -231,11 +280,12 @@
231 280 // a glance. Per-user.
232 281 'showPostStatusRibbons' => true,
233 282 // Unlocks developer-facing surfaces meant for plugin
234 283 // authors: the Starter Widget appears in the add-widget
235 - // picker, and the OS Settings → Components tab runs its
236 - // intentional missing-import-warner demo. Off by default.
237 - // Per-user.
284 + // picker, the OS Settings → Components tab runs its
285 + // intentional missing-import-warner demo, and the Code Blue
286 + // error-log reader registers (icon, window, REST routes).
287 + // Off by default. Per-user.
238 288 'developerModeEnabled' => false,
239 289 // Per-user opt-OUT for the folder-sharing feature. Defaults
240 290 // ON. When false:
241 291 // - The Share button, share-settings modal, "Leave shared
@@ -250,23 +300,30 @@
250 300 // disappears without any database changes. The site-wide
251 301 // "Delete folder sharing data" action in OS Settings →
252 302 // Features → Advanced is a separate destructive cleanup.
253 303 'foldersSharingEnabled' => true,
254 - // Per-item placement preferences. Map of item id (dock-item
255 - // slug or registered desktop-icon id) → one of:
256 - // 'both' — show on both dock and desktop.
257 - // 'dock' — show only on the dock; hide from desktop.
258 - // 'desktop' — show only on the wallpaper; hide from dock.
304 + // Per-item navigation placement. Map of item id → one of:
305 + // 'both' — show on a rail and on the desktop.
306 + // 'rail' — show only on a rail: the dock, or the sidebar
307 + // for a Core admin menu in the split layout.
308 + // 'desktop' — show only on the wallpaper.
259 309 // 'hidden' — hide from every shell surface.
260 - // Missing keys mean "no override" — items use their native rail.
310 + // Missing keys mean "no override" — the item takes the default
311 + // for its kind, which lives in `src/nav/defaults.ts`.
261 312 // Sanitized as map<sanitize_key, enum>. Capped at 256 entries.
262 - 'itemVisibility' => array(),
263 - // Per-user dock ordering. Ordered list of item ids; ids not in
264 - // the list keep their server-supplied position appended after
265 - // the listed ones. Unknown ids are tolerated.
266 - 'dockOrder' => array(),
267 - // Persisted desktop position for every dock item the user has
268 - // promoted to the wallpaper via `itemVisibility[id]=desktop|both`.
313 + 'navPlacement' => array(),
314 + // Per-user ordering, flat across every dock/sidebar zone. Ids
315 + // not in the list keep their registration order and render
316 + // after the listed ones. Unknown ids are tolerated.
317 + 'navOrder' => array(),
318 + // Which experience the shell renders: 'auto' follows the
319 + // viewport, 'desktop' / 'mobile' force it. See `includes/mobile.php`.
320 + 'mobileLayout' => 'auto',
321 + // Ids pinned to the phone tab bar, at most three. Empty means
322 + // the server default (`openstation_mobile_tab_bar`).
323 + 'mobileTabs' => array(),
324 + // Persisted desktop position for every item the user has
325 + // promoted to the wallpaper via `navPlacement[id]=desktop|both`.
269 326 // Keyed by item id, value is `{ x: int, y: int }`. The JS
270 327 // synthesizer reads this when building a synthetic placement so
271 328 // the icon lands where the user last dragged it instead of
272 329 // resetting to (0, 0) on every reload. Capped at 256 entries.
@@ -285,14 +342,14 @@
285 342 */
286 343 function openstation_get_os_settings( $user_id ) {
287 344 $user_id = (int) $user_id;
288 345 if ( $user_id <= 0 ) {
289 - return openstation_default_os_settings();
346 + return openstation_sanitize_os_settings( array() );
290 347 }
291 348
292 349 $raw = get_user_meta( $user_id, OPENSTATION_OS_SETTINGS_META_KEY, true );
293 350 if ( ! is_array( $raw ) ) {
294 - return openstation_default_os_settings();
351 + return openstation_sanitize_os_settings( array() );
295 352 }
296 353
297 354 return openstation_sanitize_os_settings( $raw );
298 355 }
@@ -314,8 +371,76 @@
314 371 return false !== update_user_meta( $user_id, OPENSTATION_OS_SETTINGS_META_KEY, $clean );
315 372 }
316 373
317 374 /**
375 + * Strip the rail-synthesis prefix an id could carry before the
376 + * navigation model.
377 + *
378 + * `dock:<id>` / `desktop:<id>` used to mean "this tile is a copy of an
379 + * item whose real home is the other rail". Nothing synthesizes copies
380 + * any more — an item is one item wherever it is painted — so the
381 + * prefix is noise, and left in place it would key a preference to an
382 + * id nothing registers.
383 + *
384 + * @param string $id Possibly-prefixed id.
385 + * @return string Canonical id.
386 + */
387 +function openstation_canonical_nav_id( $id ) {
388 + $id = (string) $id;
389 + if ( 0 === strpos( $id, 'dock:' ) ) {
390 + return substr( $id, 5 );
391 + }
392 + if ( 0 === strpos( $id, 'desktop:' ) ) {
393 + return substr( $id, 8 );
394 + }
395 + return $id;
396 +}
397 +
398 +/**
399 + * Carry a pre-navigation `itemVisibility` map into `navPlacement`.
400 + *
401 + * The only value that moves is `'dock'` → `'rail'`: the stored name
402 + * is now the REGION rather than a rail, so a Core admin menu the user
403 + * kept on a rail follows the layout into the sidebar instead of
404 + * needing a second migration the first time they switch.
405 + *
406 + * Runs on read (see {@see openstation_sanitize_os_settings()}) rather
407 + * than as a numbered migration, because OS settings are per-user meta
408 + * and a site with many users would pay for a sweep that the next save
409 + * performs for free.
410 + *
411 + * @param array $visibility Legacy map of item id → placement.
412 + * @return array Map of canonical item id → nav placement.
413 + */
414 +function openstation_migrate_item_visibility( $visibility ) {
415 + $map = array(
416 + 'dock' => 'rail',
417 + 'desktop' => 'desktop',
418 + 'both' => 'both',
419 + 'hidden' => 'hidden',
420 + );
421 +
422 + $out = array();
423 + foreach ( (array) $visibility as $key => $val ) {
424 + if ( ! is_string( $key ) || ! is_string( $val ) || ! isset( $map[ $val ] ) ) {
425 + continue;
426 + }
427 + $id = openstation_canonical_nav_id( $key );
428 + if ( '' === $id ) {
429 + continue;
430 + }
431 + // A prefixed and an unprefixed key can collapse onto the same
432 + // id. The unprefixed one is the item's own preference rather
433 + // than a synthesized copy's, so it wins whichever order they
434 + // arrive in.
435 + if ( $id === $key || ! isset( $out[ $id ] ) ) {
436 + $out[ $id ] = $map[ $val ];
437 + }
438 + }
439 + return $out;
440 +}
441 +
442 +/**
318 443 * Sanitizes a raw OS settings payload.
319 444 *
320 445 * Unknown keys are ignored; known keys are coerced field-by-field so a
321 446 * partial save (e.g., only accent changed) merges cleanly with the
@@ -327,9 +452,9 @@
327 452 function openstation_sanitize_os_settings( $raw ) {
328 453 $defaults = openstation_default_os_settings();
329 454
330 455 if ( ! is_array( $raw ) ) {
331 - return $defaults;
456 + $raw = array();
332 457 }
333 458
334 459 // Wallpaper — any non-empty string; registry membership is validated
335 460 // client-side at apply time.
@@ -367,8 +492,19 @@
367 492 && in_array( $raw['adminBarMode'], OPENSTATION_OS_SETTINGS_ADMIN_BAR_MODES, true )
368 493 ? (string) $raw['adminBarMode']
369 494 : $defaults['adminBarMode'];
370 495
496 + // Dock behavior — must be one of the two known values. One answer
497 + // per rail: the dock, and the Split layout's sidebar.
498 + $dock_behavior = isset( $raw['dockBehavior'] )
499 + && in_array( $raw['dockBehavior'], OPENSTATION_OS_SETTINGS_DOCK_BEHAVIORS, true )
500 + ? (string) $raw['dockBehavior']
501 + : $defaults['dockBehavior'];
502 + $side_dock_behavior = isset( $raw['sideDockBehavior'] )
503 + && in_array( $raw['sideDockBehavior'], OPENSTATION_OS_SETTINGS_DOCK_BEHAVIORS, true )
504 + ? (string) $raw['sideDockBehavior']
505 + : $defaults['sideDockBehavior'];
506 +
371 507 // Desktop layout — must be one of the known values (`classic`,
372 508 // `unified`). Default `unified`.
373 509 $desktop_layout = isset( $raw['desktopLayout'] )
374 510 && in_array( $raw['desktopLayout'], OPENSTATION_OS_SETTINGS_DESKTOP_LAYOUTS, true )
@@ -649,8 +785,27 @@
649 785 // user meta indefinitely.
650 786 $native_posts_hidden_columns = array_slice( array_values( array_unique( $native_posts_hidden_columns ) ), 0, 32 );
651 787 }
652 788
789 + $native_pages_hidden_columns = $defaults['nativePagesHiddenColumns'];
790 + if ( isset( $raw['nativePagesHiddenColumns'] ) && is_array( $raw['nativePagesHiddenColumns'] ) ) {
791 + $native_pages_hidden_columns = array();
792 + foreach ( $raw['nativePagesHiddenColumns'] as $col ) {
793 + if ( ! is_string( $col ) || '' === $col ) {
794 + continue;
795 + }
796 + $slug = sanitize_key( $col );
797 + if ( '' === $slug ) {
798 + continue;
799 + }
800 + $native_pages_hidden_columns[] = $slug;
801 + }
802 + // Cap to a sane upper bound — far more than any plausible
803 + // column count, but blocks a malicious payload from bloating
804 + // user meta indefinitely.
805 + $native_pages_hidden_columns = array_slice( array_values( array_unique( $native_pages_hidden_columns ) ), 0, 32 );
806 + }
807 +
653 808 $native_pages_enabled = isset( $raw['nativePagesEnabled'] )
654 809 ? (bool) $raw['nativePagesEnabled']
655 810 : $defaults['nativePagesEnabled'];
656 811
@@ -665,15 +820,29 @@
665 820 $native_comments_enabled = isset( $raw['nativeCommentsEnabled'] )
666 821 ? (bool) $raw['nativeCommentsEnabled']
667 822 : $defaults['nativeCommentsEnabled'];
668 823
824 + $station_home_enabled = isset( $raw['stationHomeEnabled'] )
825 + ? (bool) $raw['stationHomeEnabled']
826 + : $defaults['stationHomeEnabled'];
827 +
828 + // Site-wide performance controls supersede the legacy per-user values.
829 + // Keep the snapshot keys so existing window consumers can read them.
830 + $extended_options = openstation_get_extended_options();
831 + $admin_asset_cache_enabled = $extended_options['admin_asset_cache'];
832 + $window_prewarm_enabled = $extended_options['window_prewarm'];
833 +
669 834 $show_desktop_on_wallpaper_click = isset( $raw['showDesktopOnWallpaperClick'] )
670 835 ? (bool) $raw['showDesktopOnWallpaperClick']
671 836 : $defaults['showDesktopOnWallpaperClick'];
672 837
838 + $confirm_close_all_windows = isset( $raw['confirmCloseAllWindows'] )
839 + ? (bool) $raw['confirmCloseAllWindows']
840 + : $defaults['confirmCloseAllWindows'];
841 +
673 842 $mio_enabled = isset( $raw['mioEnabled'] )
674 843 ? (bool) $raw['mioEnabled']
675 - : $defaults['mioEnabled'];
844 + : ( isset( $raw['mioApiEnabled'] ) ? (bool) $raw['mioApiEnabled'] : $defaults['mioEnabled'] );
676 845
677 846 // A missing key means "no look saved yet", which sanitizes to the
678 847 // same pair of empty arrays the defaults carry — so this needs no
679 848 // isset() branch of its own.
@@ -688,20 +857,59 @@
688 857 $developer_mode_enabled = isset( $raw['developerModeEnabled'] )
689 858 ? (bool) $raw['developerModeEnabled']
690 859 : $defaults['developerModeEnabled'];
691 860
861 + // mobileLayout — the phone/desktop override.
862 + $mobile_layout = isset( $raw['mobileLayout'] )
863 + && in_array( $raw['mobileLayout'], OPENSTATION_OS_SETTINGS_MOBILE_LAYOUTS, true )
864 + ? (string) $raw['mobileLayout']
865 + : $defaults['mobileLayout'];
866 +
867 + // mobileTabs — ordered nav ids pinned to the phone tab bar. Same
868 + // id grammar as navOrder, capped at the tab bar's slot count.
869 + $mobile_tabs = array();
870 + if ( isset( $raw['mobileTabs'] ) && is_array( $raw['mobileTabs'] ) ) {
871 + $seen_tabs = array();
872 + foreach ( $raw['mobileTabs'] as $id ) {
873 + if ( ! is_string( $id ) || '' === $id ) {
874 + continue;
875 + }
876 + $slug = sanitize_key( openstation_canonical_nav_id( $id ) );
877 + if ( '' === $slug || isset( $seen_tabs[ $slug ] ) ) {
878 + continue;
879 + }
880 + $seen_tabs[ $slug ] = true;
881 + $mobile_tabs[] = $slug;
882 + if ( count( $mobile_tabs ) >= OPENSTATION_OS_SETTINGS_MOBILE_TABS_MAX ) {
883 + break;
884 + }
885 + }
886 + }
887 +
692 888 $folders_sharing_enabled = isset( $raw['foldersSharingEnabled'] )
693 889 ? (bool) $raw['foldersSharingEnabled']
694 890 : $defaults['foldersSharingEnabled'];
695 891
696 - // itemVisibility — map<sanitize_key, enum>. Unknown ids are kept
892 + // navPlacement — map<sanitize_key, enum>. Unknown ids are kept
697 893 // (a deactivated plugin's setting should survive reactivation);
698 894 // invalid placement values are dropped.
699 - $item_visibility = array();
700 - if ( isset( $raw['itemVisibility'] ) && is_array( $raw['itemVisibility'] ) ) {
701 - $allowed_placements = array( 'both', 'dock', 'desktop', 'hidden' );
895 + //
896 + // Reads the pre-navigation `itemVisibility` map when this user has
897 + // no `navPlacement` yet, so an existing arrangement carries over on
898 + // first load and is written back on the next save. See
899 + // `openstation_migrate_item_visibility()`.
900 + $raw_placement = array();
901 + if ( isset( $raw['navPlacement'] ) && is_array( $raw['navPlacement'] ) ) {
902 + $raw_placement = $raw['navPlacement'];
903 + } elseif ( isset( $raw['itemVisibility'] ) && is_array( $raw['itemVisibility'] ) ) {
904 + $raw_placement = openstation_migrate_item_visibility( $raw['itemVisibility'] );
905 + }
906 +
907 + $nav_placement = array();
908 + if ( ! empty( $raw_placement ) ) {
909 + $allowed_placements = array( 'both', 'rail', 'desktop', 'hidden' );
702 910 $count = 0;
703 - foreach ( $raw['itemVisibility'] as $key => $val ) {
911 + foreach ( $raw_placement as $key => $val ) {
704 912 if ( $count >= 256 ) {
705 913 break;
706 914 }
707 915 if ( ! is_string( $key ) || '' === $key || ! is_string( $val ) ) {
@@ -713,34 +921,38 @@
713 921 }
714 922 if ( ! in_array( $val, $allowed_placements, true ) ) {
715 923 continue;
716 924 }
717 - $item_visibility[ $slug ] = $val;
925 + $nav_placement[ $slug ] = $val;
718 926 ++$count;
719 927 }
720 928 }
721 929
722 - // dockOrder — ordered list of item ids. Most are sanitize_key()-
723 - // clean dock slugs, but cross-rail tiles the user promoted carry a
724 - // rail-synthesis prefix (`desktop:<id>` / `dock:<id>`, built by
725 - // src/settings/item-placement.ts). sanitize_key() strips the colon,
726 - // which silently breaks the JS order match on reload and can collide
727 - // with an unrelated id — so allow the colon (and hyphen/underscore)
728 - // while still rejecting anything outside the JS id charset.
729 - $dock_order = array();
730 - if ( isset( $raw['dockOrder'] ) && is_array( $raw['dockOrder'] ) ) {
930 + // navOrder — ordered list of item ids, flat across every zone.
931 + // Reads the pre-navigation `dockOrder` when absent, stripping the
932 + // rail-synthesis prefixes (`dock:` / `desktop:`) that model no
933 + // longer has.
934 + $raw_order = array();
935 + if ( isset( $raw['navOrder'] ) && is_array( $raw['navOrder'] ) ) {
936 + $raw_order = $raw['navOrder'];
937 + } elseif ( isset( $raw['dockOrder'] ) && is_array( $raw['dockOrder'] ) ) {
938 + $raw_order = $raw['dockOrder'];
939 + }
940 +
941 + $nav_order = array();
942 + if ( ! empty( $raw_order ) ) {
731 943 $seen = array();
732 - foreach ( $raw['dockOrder'] as $id ) {
944 + foreach ( $raw_order as $id ) {
733 945 if ( ! is_string( $id ) || '' === $id ) {
734 946 continue;
735 947 }
736 - $slug = (string) preg_replace( '/[^a-z0-9_:-]+/', '', strtolower( $id ) );
948 + $slug = sanitize_key( openstation_canonical_nav_id( $id ) );
737 949 if ( '' === $slug || isset( $seen[ $slug ] ) ) {
738 950 continue;
739 951 }
740 952 $seen[ $slug ] = true;
741 - $dock_order[] = $slug;
742 - if ( count( $dock_order ) >= 256 ) {
953 + $nav_order[] = $slug;
954 + if ( count( $nav_order ) >= 256 ) {
743 955 break;
744 956 }
745 957 }
746 958 }
@@ -794,8 +1006,10 @@
794 1006 'windowRadius' => $window_radius,
795 1007 'adminBarMode' => $admin_bar_mode,
796 1008 'desktopLayout' => $desktop_layout,
797 1009 'dockPlacement' => $dock_placement,
1010 + 'dockBehavior' => $dock_behavior,
1011 + 'sideDockBehavior' => $side_dock_behavior,
798 1012 'dockRailRenderer' => $dock_rail_renderer,
799 1013 'desktopTheme' => $desktop_theme,
800 1014 'appliedThemeRecommendations' => $applied_theme_recommendations,
801 1015 'unfocusEffect' => $unfocus_effect,
@@ -814,19 +1028,28 @@
814 1028 'heartbeatRate' => $heartbeat_rate,
815 1029 'nativePostsEnabled' => $native_posts_enabled,
816 1030 'nativePostsHiddenColumns' => $native_posts_hidden_columns,
817 1031 'nativePagesEnabled' => $native_pages_enabled,
1032 + 'nativePagesHiddenColumns' => $native_pages_hidden_columns,
818 1033 'nativeUsersEnabled' => $native_users_enabled,
819 1034 'nativePluginsEnabled' => $native_plugins_enabled,
820 1035 'nativeCommentsEnabled' => $native_comments_enabled,
1036 + 'stationHomeEnabled' => $station_home_enabled,
1037 + 'adminAssetCacheEnabled' => $admin_asset_cache_enabled,
1038 + 'windowPrewarmEnabled' => $window_prewarm_enabled,
821 1039 'showDesktopOnWallpaperClick' => $show_desktop_on_wallpaper_click,
1040 + 'confirmCloseAllWindows' => $confirm_close_all_windows,
822 1041 'mioEnabled' => $mio_enabled,
1042 + 'mioApiEnabled' => $mio_enabled,
1043 + 'mioShowOnWallpaper' => isset( $raw['mioShowOnWallpaper'] ) ? (bool) $raw['mioShowOnWallpaper'] : $defaults['mioShowOnWallpaper'],
823 1044 'mioStyle' => $mio_style,
824 1045 'showPostStatusRibbons' => $show_post_status_ribbons,
825 1046 'developerModeEnabled' => $developer_mode_enabled,
826 1047 'foldersSharingEnabled' => $folders_sharing_enabled,
827 - 'itemVisibility' => $item_visibility,
828 - 'dockOrder' => $dock_order,
1048 + 'navPlacement' => $nav_placement,
1049 + 'navOrder' => $nav_order,
1050 + 'mobileLayout' => $mobile_layout,
1051 + 'mobileTabs' => $mobile_tabs,
829 1052 'dockPromotedPositions' => $dock_promoted_positions,
830 1053 );
831 1054 }
832 1055
@@ -902,9 +1125,9 @@
902 1125 * defaults. Give the saver merge semantics and that migration
903 1126 * silently becomes a no-op.
904 1127 *
905 1128 * Merging is shallow, one level deep. For the map-shaped fields
906 - * (`wallpaperSettings`, `itemVisibility`, `dockOrder`,
1129 + * (`wallpaperSettings`, `navPlacement`, `navOrder`,
907 1130 * `dockPromotedPositions`) a request that sends the key replaces the
908 1131 * whole map — deep-merging them would leave no way to delete an
909 1132 * entry.
910 1133 *
@@ -926,8 +1149,13 @@
926 1149 // true of every path into this handler, not just the ones the
927 1150 // schema happens to guard.
928 1151 if ( ! is_array( $payload ) ) {
929 1152 return rest_ensure_response( openstation_get_os_settings( $user_id ) );
1153 + }
1154 +
1155 + // Normalize an alias-only patch before merging it with the saved master value.
1156 + if ( ! array_key_exists( 'mioEnabled', $payload ) && array_key_exists( 'mioApiEnabled', $payload ) ) {
1157 + $payload['mioEnabled'] = $payload['mioApiEnabled'];
930 1158 }
931 1159
932 1160 openstation_save_os_settings(
933 1161 $user_id,