PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.0.0
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.0.0
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
desktop-mode / includes / assets.php

assets.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.0.0, at includes/assets.php

593 lines 23.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation asset registration.
4 *
5 * Registers all openstation CSS and JS handles with WordPress so they can
6 * be enqueued from anywhere in the plugin (or by third parties).
7 *
8 * @package OpenStation
9 */
10
11 defined( 'ABSPATH' ) || exit;
12
13 /**
14 * Cache-buster for a stylesheet that `@import`s sub-sheets: the max
15 * `filemtime` across the file AND everything it (transitively) imports.
16 * Sub-sheet URLs carry no `?ver=` of their own, so the parent's stamp is
17 * the only cache key the browser ever sees for the subtree — it must
18 * move when any member changes.
19 *
20 * @param string $relative Stylesheet path relative to the plugin dir.
21 * @param string $fallback Version to use when the file is missing.
22 * @return string Version string.
23 */
24 function openstation_css_subtree_version( $relative, $fallback ) {
25 $root = OPENSTATION_DIR . $relative;
26 if ( ! file_exists( $root ) ) {
27 return (string) $fallback;
28 }
29 $max = (int) filemtime( $root );
30 $queue = array( $root );
31 $seen = array( $root => true );
32 while ( ! empty( $queue ) ) {
33 $file = array_pop( $queue );
34 $css = (string) file_get_contents( $file );
35 if ( ! preg_match_all( '/@import\s+url\(\s*["\']?([^"\')\s]+)["\']?\s*\)/i', $css, $matches ) ) {
36 continue;
37 }
38 foreach ( $matches[1] as $import ) {
39 $path = dirname( $file ) . '/' . $import;
40 if ( ! file_exists( $path ) || isset( $seen[ $path ] ) ) {
41 continue;
42 }
43 $seen[ $path ] = true;
44 $max = max( $max, (int) filemtime( $path ) );
45 $queue[] = $path;
46 }
47 }
48 return (string) $max;
49 }
50
51 /**
52 * Registers the OpenStation CSS and JS handles.
53 */
54 function openstation_register_assets() {
55 $version = OPENSTATION_VERSION;
56 $suffix = openstation_asset_suffix();
57
58 // `filemtime`-stamped version for built bundles. The plugin-wide
59 // `OPENSTATION_VERSION` is bumped per release, but the bundles
60 // iterate faster — without a per-file mtime stamp, two clients
61 // loading the same `?ver=…` URL can be served different bytes
62 // (whichever build was on disk at upload time). Stamping with the
63 // file's modification time guarantees the URL changes whenever the
64 // file does, so "is my fix deployed?" is answerable from the
65 // network tab. Falls back to `$version` when the file is missing
66 // (test envs that import this file before the build runs).
67 $built_version = static function ( $relative ) use ( $version ) {
68 $path = OPENSTATION_DIR . $relative;
69 return file_exists( $path ) ? (string) filemtime( $path ) : $version;
70 };
71
72 // Styles.
73 // `filemtime`-stamped, NOT the plugin-wide `$version`. This file
74 // is the token catalogue every other sheet resolves `var()`s
75 // against, and it changes whenever the palette does — which is a
76 // lot more often than the plugin version is bumped. With a static
77 // stamp the browser holds the old palette until a hard reload,
78 // and the symptom is maddening: a themed shell where some
79 // surfaces update and others don't.
80 wp_register_style(
81 'os-variables',
82 OPENSTATION_URL . 'assets/css/variables.css',
83 array(),
84 $built_version( 'assets/css/variables.css' )
85 );
86 // `filemtime`-stamped so the `<link rel="stylesheet">` URL matches the
87 // `<link rel="preload" as="style">` hint emitted by
88 // `openstation_print_preload_hints()` (which stamps with filemtime).
89 // Registering this with the plain `$version` instead produced two
90 // different `?ver=` query strings for the same file, so the browser
91 // never matched the preload to the stylesheet and logged "preloaded
92 // but not used within a few seconds from the window's load event".
93 wp_register_style(
94 'openstation',
95 OPENSTATION_URL . 'assets/css/desktop.css',
96 array( 'os-variables' ),
97 $built_version( 'assets/css/desktop.css' )
98 );
99
100 /*
101 * Window styles — one handle per sheet, chained by dependency.
102 *
103 * These used to be `@import url( … )`ed from `windows.css` under
104 * the single `os-windows` handle. That was a standing
105 * cache bug: an `@import` URL carries no `?ver=`, so a changed
106 * sub-sheet had no URL for the browser to invalidate. Stamping the
107 * PARENT with the subtree's max mtime (which is what
108 * `openstation_css_subtree_version()` was for) made the browser
109 * re-fetch `windows.css` and then request each sub-sheet at an
110 * unchanged URL — free to be served from its heuristic cache. The
111 * result was edits not landing until a hard refresh, and rules
112 * being relocated into `windows.css` purely to dodge it.
113 *
114 * Now every sheet is separately registered with its own
115 * `filemtime` stamp, so each has a real cache key.
116 *
117 * THE DEPENDENCY CHAIN IS LOAD-BEARING. WordPress prints
118 * dependencies before dependents, so chaining each sheet to the
119 * previous one reproduces the order the `@import` block had, and
120 * `os-windows` (which depends on the last link) still
121 * prints after them and still wins ties:
122 *
123 * window-chrome → window-states → effects → window-links
124 * → windows
125 *
126 * `window-overview` and `os-settings` are deliberately NOT in this
127 * chain — they are registered below, after `os-windows`,
128 * so they can load deferred. Adding a sheet here means splicing it
129 * into the chain, not appending an unrelated dependency: order is
130 * the contract.
131 */
132 $window_sheets = array(
133 'os-window-chrome' => 'assets/css/window-chrome.css',
134 'os-window-states' => 'assets/css/window-states.css',
135 'os-effects' => 'assets/css/effects.css',
136 'os-window-links' => 'assets/css/window-links.css',
137 );
138 $previous = array( 'os-variables', 'dashicons' );
139 foreach ( $window_sheets as $handle => $relative ) {
140 wp_register_style(
141 $handle,
142 OPENSTATION_URL . $relative,
143 $previous,
144 $built_version( $relative )
145 );
146 $previous = array( $handle );
147 }
148
149 // Entry point. Depends on the tail of the chain above, so
150 // enqueuing this one handle still pulls in every critical window
151 // sheet — the behaviour callers had when they were `@import`s.
152 wp_register_style(
153 'os-windows',
154 OPENSTATION_URL . 'assets/css/windows.css',
155 $previous,
156 $built_version( 'assets/css/windows.css' )
157 );
158 // These two load DEFERRED (see `openstation_defer_non_critical_styles()`):
159 // the UI they style — the OS Settings panel and the window
160 // overview — is lazy-loaded JS that can never be on screen at
161 // first paint, so ~47 KB of CSS has no business blocking render.
162 // They depend on `os-windows` so they print after it,
163 // preserving the cascade position they had as `@import`s.
164 wp_register_style(
165 'os-window-overview',
166 OPENSTATION_URL . 'assets/css/window-overview.css',
167 array( 'os-windows' ),
168 $built_version( 'assets/css/window-overview.css' )
169 );
170 wp_register_style(
171 'os-settings',
172 OPENSTATION_URL . 'assets/css/os-settings.css',
173 array( 'os-windows' ),
174 $built_version( 'assets/css/os-settings.css' )
175 );
176 wp_register_style(
177 'os-dock',
178 OPENSTATION_URL . 'assets/css/dock.css',
179 array( 'os-variables', 'dashicons' ),
180 $built_version( 'assets/css/dock.css' )
181 );
182 wp_register_style(
183 'os-dock-peek',
184 OPENSTATION_URL . 'assets/css/dock-peek.css',
185 array( 'os-dock' ),
186 $built_version( 'assets/css/dock-peek.css' )
187 );
188 // `filemtime`-stamped — the chromeless overrides iterate faster
189 // than the plugin-wide version bumps (per-page compat shims and
190 // page-title-action exceptions land in patches), and a stale
191 // cached copy means the user sees yesterday's rules. Without the
192 // stamp, the browser keeps `?ver=0.8.1` valid for the whole
193 // release cycle.
194 wp_register_style(
195 'os-chromeless',
196 OPENSTATION_URL . 'assets/css/chromeless.css',
197 array( 'openstation' ),
198 $built_version( 'assets/css/chromeless.css' )
199 );
200
201 // `filemtime`-stamped — the AI assistant surface (error states,
202 // inline affordances) iterates faster than plugin version bumps.
203 // Without an mtime stamp the browser keeps `?ver=<plugin-version>`
204 // valid for the whole release cycle and the user keeps seeing
205 // yesterday's CSS even after a hard reload.
206 wp_register_style(
207 'desktop-mode-ai-assistant',
208 OPENSTATION_URL . 'assets/css/ai-assistant.css',
209 array( 'os-variables' ),
210 $built_version( 'assets/css/ai-assistant.css' )
211 );
212
213 wp_register_style(
214 'desktop-mode-bug-report',
215 OPENSTATION_URL . 'assets/css/bug-report.css',
216 array( 'os-variables' ),
217 $built_version( 'assets/css/bug-report.css' )
218 );
219
220 // `filemtime` instead of the plugin-wide `$version` for the
221 // recycle-bin CSS — this file iterates faster than the bundle
222 // and we never want a stale CSS cache to mask a real fix.
223 $recycle_bin_css = OPENSTATION_DIR . 'assets/css/recycle-bin.css';
224 wp_register_style(
225 'desktop-mode-recycle-bin',
226 OPENSTATION_URL . 'assets/css/recycle-bin.css',
227 array( 'os-variables', 'dashicons' ),
228 file_exists( $recycle_bin_css ) ? (string) filemtime( $recycle_bin_css ) : $version
229 );
230
231 // `filemtime` for the native Posts window CSS — same rationale as
232 // the recycle-bin CSS: bundle iterates faster than the plugin
233 // version and stale caches are worse than the cost of a 304.
234 $posts_window_css = OPENSTATION_DIR . 'assets/css/posts-window.css';
235 wp_register_style(
236 'os-posts-window',
237 OPENSTATION_URL . 'assets/css/posts-window.css',
238 array( 'os-variables', 'dashicons' ),
239 file_exists( $posts_window_css ) ? (string) filemtime( $posts_window_css ) : $version
240 );
241
242 // Native Plugins window CSS — same `filemtime`-cache-bust posture
243 // as the Posts/Recycle Bin styles.
244 $plugins_window_css = OPENSTATION_DIR . 'assets/css/plugins-window.css';
245 wp_register_style(
246 'os-plugins-window',
247 OPENSTATION_URL . 'assets/css/plugins-window.css',
248 array( 'os-variables', 'dashicons' ),
249 file_exists( $plugins_window_css ) ? (string) filemtime( $plugins_window_css ) : $version
250 );
251
252 // Native Comments window CSS — same `filemtime` posture.
253 $comments_window_css = OPENSTATION_DIR . 'assets/css/comments-window.css';
254 wp_register_style(
255 'os-comments-window',
256 OPENSTATION_URL . 'assets/css/comments-window.css',
257 array( 'os-variables', 'dashicons' ),
258 file_exists( $comments_window_css ) ? (string) filemtime( $comments_window_css ) : $version
259 );
260
261 // Files-on-the-Desktop tile + layer styles. `filemtime` for the
262 // same reason as the recycle-bin / posts-window CSS: this file
263 // iterates faster than the plugin version, and a stale cache
264 // would mask a real fix.
265 $desktop_files_css = OPENSTATION_DIR . 'assets/css/desktop-files.css';
266 wp_register_style(
267 'os-files',
268 OPENSTATION_URL . 'assets/css/desktop-files.css',
269 array( 'os-variables', 'dashicons' ),
270 file_exists( $desktop_files_css ) ? (string) filemtime( $desktop_files_css ) : $version
271 );
272
273 // Games hub window (launcher grid, scoreboard, challenges) +
274 // per-game styles. Same `filemtime` cache-bust posture as the
275 // other fast-iterating feature stylesheets.
276 $games_css = OPENSTATION_DIR . 'assets/css/games.css';
277 wp_register_style(
278 'desktop-mode-games',
279 OPENSTATION_URL . 'assets/css/games.css',
280 array( 'os-variables', 'dashicons' ),
281 file_exists( $games_css ) ? (string) filemtime( $games_css ) : $version
282 );
283 $game_inkfall_css = OPENSTATION_DIR . 'assets/css/game-inkfall.css';
284 wp_register_style(
285 'os-game-inkfall',
286 OPENSTATION_URL . 'assets/css/game-inkfall.css',
287 array( 'os-variables' ),
288 file_exists( $game_inkfall_css ) ? (string) filemtime( $game_inkfall_css ) : $version
289 );
290 $game_alphabet_soup_css = OPENSTATION_DIR . 'assets/css/game-alphabet-soup.css';
291 wp_register_style(
292 'os-game-alphabet-soup',
293 OPENSTATION_URL . 'assets/css/game-alphabet-soup.css',
294 array( 'os-variables' ),
295 file_exists( $game_alphabet_soup_css ) ? (string) filemtime( $game_alphabet_soup_css ) : $version
296 );
297
298 // Pinned-notes layer styles (paper, pushpin, pastel tokens, pin
299 // animations). Same `filemtime` cache-bust posture as the other
300 // fast-iterating feature stylesheets above.
301 $notes_css = OPENSTATION_DIR . 'assets/css/notes.css';
302 wp_register_style(
303 'os-notes',
304 OPENSTATION_URL . 'assets/css/notes.css',
305 array( 'os-variables', 'dashicons' ),
306 file_exists( $notes_css ) ? (string) filemtime( $notes_css ) : $version
307 );
308
309 // Announcement-dialog styles (scrim, hero card, buttons). Registered
310 // always, enqueued only for a user who is actually owed the
311 // announcement — see the gate in `openstation_enqueue_assets()`. It
312 // is not lazy-loaded on top of that: on the one boot where it does
313 // go out, the dialog opens from the main bundle a second later, and
314 // a fetch racing that would show an unstyled card.
315 $announce_css = OPENSTATION_DIR . 'assets/css/announce.css';
316 wp_register_style(
317 'os-announce',
318 OPENSTATION_URL . 'assets/css/announce.css',
319 array( 'os-variables' ),
320 file_exists( $announce_css ) ? (string) filemtime( $announce_css ) : $version
321 );
322
323 // Scripts.
324 //
325 // `wp-hooks` — the shell exposes a WordPress-style filter/action
326 // API (`window.wp.hooks`) to third-party plugins.
327 // `wp-i18n` — the TS `__()` / `_x()` / `sprintf()` wrappers in
328 // `src/i18n.ts` delegate to `window.wp.i18n` for translation
329 // lookups. Both handles are core-shipped but only pre-enqueued
330 // when Gutenberg-adjacent deps pull them in, so we list them
331 // explicitly to guarantee load order.
332 wp_register_script(
333 'openstation',
334 OPENSTATION_URL . 'assets/js/desktop' . $suffix . '.js',
335 // `heartbeat` + `jquery` — the recycle-bin badge module
336 // (loaded as part of this bundle) opts into the WordPress
337 // Heartbeat API so the count tile / desktop-icon badge
338 // stays in sync even when the bin window is closed.
339 array( 'wp-hooks', 'wp-i18n', 'heartbeat', 'jquery' ),
340 $built_version( 'assets/js/desktop' . $suffix . '.js' ),
341 // Footer + defer: the shell boots on DOMContentLoaded anyway,
342 // so deferring frees the parser instead of blocking at the
343 // footer print point. Both inline payloads attached to this
344 // handle (`__openStationMenuCommands`, the jazz-quote version
345 // stamp) are `'before'`-position, which WP keeps as blocking
346 // inline ahead of a deferred tag — order is preserved. On WP
347 // < 6.3 the array collapses to a truthy `$in_footer`, same as
348 // before.
349 array(
350 'in_footer' => true,
351 'strategy' => 'defer',
352 )
353 );
354
355 // `os-iframe-bridge` — opt-in iframe-side bridge that
356 // provides `wp.os.iframe.publish/subscribe/onConnection/
357 // requestConnection` to any same-origin iframe that enqueues it.
358 // Same code is also injected inline by the chromeless bridge
359 // (so chromeless wp-admin pages don't need a separate enqueue)
360 // and auto-injected when a native window opts in via
361 // `iframeContent: { bridge: true }`. Plugins targeting their
362 // own iframe pages just enqueue this handle.
363 wp_register_script(
364 'os-iframe-bridge',
365 OPENSTATION_URL . 'assets/js/iframe-bridge' . $suffix . '.js',
366 array(),
367 $built_version( 'assets/js/iframe-bridge' . $suffix . '.js' ),
368 true
369 );
370
371 // `os-gutenberg-drop-receiver` — iframe-side bundle
372 // enqueued only on the Block Editor screens (`post.php` /
373 // `post-new.php`) for openstation users. Listens for
374 // `os-drop` postMessages from the parent shell (see
375 // `src/drag/iframe-drop-targets.ts`) and inserts the matching
376 // Gutenberg block via `wp.data.dispatch('core/block-editor')`.
377 // Depends on `wp-blocks` + `wp-data` so the editor stores are
378 // guaranteed enqueued before the receiver runs its first message
379 // handler.
380 wp_register_script(
381 'os-gutenberg-drop-receiver',
382 OPENSTATION_URL . 'assets/js/gutenberg-drop-receiver' . $suffix . '.js',
383 array( 'wp-blocks', 'wp-data' ),
384 $built_version( 'assets/js/gutenberg-drop-receiver' . $suffix . '.js' ),
385 true
386 );
387
388 // `desktop-mode-recycle-bin` — small bundle for the Recycle Bin
389 // native window. Lazy-loaded by the native-window sync the first
390 // time the bin opens; registers a render callback on
391 // `window.openStationNativeWindows['desktop-mode-recycle-bin']`.
392 $recycle_bin_js = OPENSTATION_DIR . 'assets/js/recycle-bin' . $suffix . '.js';
393 wp_register_script(
394 'desktop-mode-recycle-bin',
395 OPENSTATION_URL . 'assets/js/recycle-bin' . $suffix . '.js',
396 // `heartbeat` + `jquery` — the bin opts in to the WordPress
397 // Heartbeat API while its window is open as the catch-all
398 // real-time channel for deletes that don't render an admin
399 // footer (REST/AJAX/other tabs/WP-CLI). See
400 // `src/recycle-bin/realtime.ts` for the subscriber.
401 array( 'wp-i18n', 'heartbeat', 'jquery' ),
402 file_exists( $recycle_bin_js ) ? (string) filemtime( $recycle_bin_js ) : $version,
403 true
404 );
405 wp_set_script_translations(
406 'desktop-mode-recycle-bin',
407 'desktop-mode',
408 OPENSTATION_DIR . 'languages'
409 );
410
411 // `desktop-mode-games` — bundle for the Games hub native window
412 // (launcher grid, scoreboard, challenges client). Lazy-loaded by
413 // the native-window sync the first time the hub opens; registers a
414 // render callback on
415 // `window.openStationNativeWindows['desktop-mode-games']`.
416 // `heartbeat` + `jquery` — the challenges client rides the
417 // WordPress Heartbeat bus for live delivery.
418 $games_js = OPENSTATION_DIR . 'assets/js/games' . $suffix . '.js';
419 wp_register_script(
420 'desktop-mode-games',
421 OPENSTATION_URL . 'assets/js/games' . $suffix . '.js',
422 array( 'wp-i18n', 'heartbeat', 'jquery' ),
423 file_exists( $games_js ) ? (string) filemtime( $games_js ) : $version,
424 true
425 );
426 wp_set_script_translations(
427 'desktop-mode-games',
428 'desktop-mode',
429 OPENSTATION_DIR . 'languages'
430 );
431
432 // `os-game-inkfall` — the Inkfall game bundle. Loaded
433 // lazily by the games framework on first launch; publishes the
434 // game def on `window.openStationGames.inkfall`.
435 $game_inkfall_js = OPENSTATION_DIR . 'assets/js/game-inkfall' . $suffix . '.js';
436 wp_register_script(
437 'os-game-inkfall',
438 OPENSTATION_URL . 'assets/js/game-inkfall' . $suffix . '.js',
439 array( 'wp-i18n' ),
440 file_exists( $game_inkfall_js ) ? (string) filemtime( $game_inkfall_js ) : $version,
441 true
442 );
443 wp_set_script_translations(
444 'os-game-inkfall',
445 'desktop-mode',
446 OPENSTATION_DIR . 'languages'
447 );
448
449 // `os-game-alphabet-soup` — the Alphabet Soup game
450 // bundle. Loaded lazily by the games framework on first launch;
451 // publishes the game def on
452 // `window.openStationGames['alphabet-soup']`.
453 $game_alphabet_soup_js = OPENSTATION_DIR . 'assets/js/game-alphabet-soup' . $suffix . '.js';
454 wp_register_script(
455 'os-game-alphabet-soup',
456 OPENSTATION_URL . 'assets/js/game-alphabet-soup' . $suffix . '.js',
457 array( 'wp-i18n' ),
458 file_exists( $game_alphabet_soup_js ) ? (string) filemtime( $game_alphabet_soup_js ) : $version,
459 true
460 );
461 wp_set_script_translations(
462 'os-game-alphabet-soup',
463 'desktop-mode',
464 OPENSTATION_DIR . 'languages'
465 );
466
467 // `os-posts-window` — small bundle for the native Posts
468 // window. Lazy-loaded by the native-window sync the first time the
469 // window opens (via the dock-click swap when the user opts in);
470 // registers a render callback on
471 // `window.openStationNativeWindows['desktop-mode-posts']`.
472 $posts_window_js = OPENSTATION_DIR . 'assets/js/posts-window' . $suffix . '.js';
473 wp_register_script(
474 'os-posts-window',
475 OPENSTATION_URL . 'assets/js/posts-window' . $suffix . '.js',
476 array( 'wp-i18n' ),
477 file_exists( $posts_window_js ) ? (string) filemtime( $posts_window_js ) : $version,
478 true
479 );
480 wp_set_script_translations(
481 'os-posts-window',
482 'desktop-mode',
483 OPENSTATION_DIR . 'languages'
484 );
485
486 // `os-plugins-window` — small bundle for the native
487 // Plugins window. Lazy-loaded by the native-window sync the first
488 // time the window opens (via the dock-click swap when the user
489 // opts in); registers a render callback on
490 // `window.openStationNativeWindows['desktop-mode-plugins']`.
491 $plugins_window_js = OPENSTATION_DIR . 'assets/js/plugins-window' . $suffix . '.js';
492 wp_register_script(
493 'os-plugins-window',
494 OPENSTATION_URL . 'assets/js/plugins-window' . $suffix . '.js',
495 array( 'wp-i18n' ),
496 file_exists( $plugins_window_js ) ? (string) filemtime( $plugins_window_js ) : $version,
497 true
498 );
499 wp_set_script_translations(
500 'os-plugins-window',
501 'desktop-mode',
502 OPENSTATION_DIR . 'languages'
503 );
504
505 // `os-comments-window` — small bundle for the native
506 // Comments window. Lazy-loaded by the native-window sync the first
507 // time the window opens (via the dock-click swap when the user
508 // opts in); registers a render callback on
509 // `window.openStationNativeWindows['desktop-mode-comments']`.
510 $comments_window_js = OPENSTATION_DIR . 'assets/js/comments-window' . $suffix . '.js';
511 wp_register_script(
512 'os-comments-window',
513 OPENSTATION_URL . 'assets/js/comments-window' . $suffix . '.js',
514 array( 'wp-i18n' ),
515 file_exists( $comments_window_js ) ? (string) filemtime( $comments_window_js ) : $version,
516 true
517 );
518 wp_set_script_translations(
519 'os-comments-window',
520 'desktop-mode',
521 OPENSTATION_DIR . 'languages'
522 );
523
524 // `os-animated-logo-wallpaper` — built-in PixiJS canvas
525 // wallpaper, moved out of `desktop.min.js`. The wallpaper
526 // `server-sync` loads this handle when the user selects the
527 // `wp-animated-logo` wallpaper (or opens OS Settings → Wallpaper
528 // and the picker pulls every registered canvas def in). The
529 // bundle's only side effect is publishing the `WallpaperDef` on
530 // `window.openStationWallpapers['wp-animated-logo']`.
531 $animated_logo_js = OPENSTATION_DIR . 'assets/js/animated-logo-wallpaper' . $suffix . '.js';
532 wp_register_script(
533 'os-animated-logo-wallpaper',
534 OPENSTATION_URL . 'assets/js/animated-logo-wallpaper' . $suffix . '.js',
535 array( 'wp-hooks' ),
536 file_exists( $animated_logo_js ) ? (string) filemtime( $animated_logo_js ) : $version,
537 true
538 );
539
540 // `os-snow-wallpaper` — built-in PixiJS canvas
541 // wallpaper: snowfall that accumulates on window tops and melts
542 // away. Same lazy-load path as the animated logo: the wallpaper
543 // `server-sync` injects this handle when the user selects the
544 // `wp-snow` wallpaper (or opens OS Settings → Wallpaper and the
545 // picker pulls the def in). The bundle's only side effect is
546 // publishing the `WallpaperDef` on
547 // `window.openStationWallpapers['wp-snow']`.
548 $snow_js = OPENSTATION_DIR . 'assets/js/snow-wallpaper' . $suffix . '.js';
549 wp_register_script(
550 'os-snow-wallpaper',
551 OPENSTATION_URL . 'assets/js/snow-wallpaper' . $suffix . '.js',
552 array( 'wp-hooks', 'wp-i18n' ),
553 file_exists( $snow_js ) ? (string) filemtime( $snow_js ) : $version,
554 true
555 );
556 wp_set_script_translations(
557 'os-snow-wallpaper',
558 'desktop-mode',
559 OPENSTATION_DIR . 'languages'
560 );
561
562 // `desktop-mode-ai-assistant` — AI Copilot spotlight overlay,
563 // moved out of `desktop.min.js`. The main bundle ships a
564 // stub matching the public `wp.os.ai` contract; the stub
565 // `<script>`-injects this handle the first time the user opens
566 // the assistant (Cmd+K or admin-bar button).
567 $ai_assistant_js = OPENSTATION_DIR . 'assets/js/ai-assistant' . $suffix . '.js';
568 wp_register_script(
569 'desktop-mode-ai-assistant',
570 OPENSTATION_URL . 'assets/js/ai-assistant' . $suffix . '.js',
571 array( 'wp-hooks', 'wp-i18n' ),
572 file_exists( $ai_assistant_js ) ? (string) filemtime( $ai_assistant_js ) : $version,
573 true
574 );
575 wp_set_script_translations(
576 'desktop-mode-ai-assistant',
577 'desktop-mode',
578 OPENSTATION_DIR . 'languages'
579 );
580
581 // Wire the translation bundle to this script handle. WP looks
582 // for `languages/os-{locale}-os.json` and
583 // injects its `locale_data` into `wp.i18n` just before the
584 // script runs — so every `__()` call resolves to the right
585 // language without any runtime fetch.
586 wp_set_script_translations(
587 'openstation',
588 'desktop-mode',
589 OPENSTATION_DIR . 'languages'
590 );
591 }
592 add_action( 'init', 'openstation_register_assets' );
593