PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.0.1
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.0.1
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.1, at includes/assets.php

602 lines 23.4 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 // Solo mode — one window, no desk around it. Loads last so it can
177 // hide surfaces the sheets above declared, and only enqueues on a
178 // solo request, so a normal shell never pays for it.
179 wp_register_style(
180 'os-solo',
181 OPENSTATION_URL . 'assets/css/solo.css',
182 array( 'os-windows' ),
183 $built_version( 'assets/css/solo.css' )
184 );
185 wp_register_style(
186 'os-dock',
187 OPENSTATION_URL . 'assets/css/dock.css',
188 array( 'os-variables', 'dashicons' ),
189 $built_version( 'assets/css/dock.css' )
190 );
191 wp_register_style(
192 'os-dock-peek',
193 OPENSTATION_URL . 'assets/css/dock-peek.css',
194 array( 'os-dock' ),
195 $built_version( 'assets/css/dock-peek.css' )
196 );
197 // `filemtime`-stamped — the chromeless overrides iterate faster
198 // than the plugin-wide version bumps (per-page compat shims and
199 // page-title-action exceptions land in patches), and a stale
200 // cached copy means the user sees yesterday's rules. Without the
201 // stamp, the browser keeps `?ver=0.8.1` valid for the whole
202 // release cycle.
203 wp_register_style(
204 'os-chromeless',
205 OPENSTATION_URL . 'assets/css/chromeless.css',
206 array( 'openstation' ),
207 $built_version( 'assets/css/chromeless.css' )
208 );
209
210 // `filemtime`-stamped — the AI assistant surface (error states,
211 // inline affordances) iterates faster than plugin version bumps.
212 // Without an mtime stamp the browser keeps `?ver=<plugin-version>`
213 // valid for the whole release cycle and the user keeps seeing
214 // yesterday's CSS even after a hard reload.
215 wp_register_style(
216 'desktop-mode-ai-assistant',
217 OPENSTATION_URL . 'assets/css/ai-assistant.css',
218 array( 'os-variables' ),
219 $built_version( 'assets/css/ai-assistant.css' )
220 );
221
222 wp_register_style(
223 'desktop-mode-bug-report',
224 OPENSTATION_URL . 'assets/css/bug-report.css',
225 array( 'os-variables' ),
226 $built_version( 'assets/css/bug-report.css' )
227 );
228
229 // `filemtime` instead of the plugin-wide `$version` for the
230 // recycle-bin CSS — this file iterates faster than the bundle
231 // and we never want a stale CSS cache to mask a real fix.
232 $recycle_bin_css = OPENSTATION_DIR . 'assets/css/recycle-bin.css';
233 wp_register_style(
234 'desktop-mode-recycle-bin',
235 OPENSTATION_URL . 'assets/css/recycle-bin.css',
236 array( 'os-variables', 'dashicons' ),
237 file_exists( $recycle_bin_css ) ? (string) filemtime( $recycle_bin_css ) : $version
238 );
239
240 // `filemtime` for the native Posts window CSS — same rationale as
241 // the recycle-bin CSS: bundle iterates faster than the plugin
242 // version and stale caches are worse than the cost of a 304.
243 $posts_window_css = OPENSTATION_DIR . 'assets/css/posts-window.css';
244 wp_register_style(
245 'os-posts-window',
246 OPENSTATION_URL . 'assets/css/posts-window.css',
247 array( 'os-variables', 'dashicons' ),
248 file_exists( $posts_window_css ) ? (string) filemtime( $posts_window_css ) : $version
249 );
250
251 // Native Plugins window CSS — same `filemtime`-cache-bust posture
252 // as the Posts/Recycle Bin styles.
253 $plugins_window_css = OPENSTATION_DIR . 'assets/css/plugins-window.css';
254 wp_register_style(
255 'os-plugins-window',
256 OPENSTATION_URL . 'assets/css/plugins-window.css',
257 array( 'os-variables', 'dashicons' ),
258 file_exists( $plugins_window_css ) ? (string) filemtime( $plugins_window_css ) : $version
259 );
260
261 // Native Comments window CSS — same `filemtime` posture.
262 $comments_window_css = OPENSTATION_DIR . 'assets/css/comments-window.css';
263 wp_register_style(
264 'os-comments-window',
265 OPENSTATION_URL . 'assets/css/comments-window.css',
266 array( 'os-variables', 'dashicons' ),
267 file_exists( $comments_window_css ) ? (string) filemtime( $comments_window_css ) : $version
268 );
269
270 // Files-on-the-Desktop tile + layer styles. `filemtime` for the
271 // same reason as the recycle-bin / posts-window CSS: this file
272 // iterates faster than the plugin version, and a stale cache
273 // would mask a real fix.
274 $desktop_files_css = OPENSTATION_DIR . 'assets/css/desktop-files.css';
275 wp_register_style(
276 'os-files',
277 OPENSTATION_URL . 'assets/css/desktop-files.css',
278 array( 'os-variables', 'dashicons' ),
279 file_exists( $desktop_files_css ) ? (string) filemtime( $desktop_files_css ) : $version
280 );
281
282 // Games hub window (launcher grid, scoreboard, challenges) +
283 // per-game styles. Same `filemtime` cache-bust posture as the
284 // other fast-iterating feature stylesheets.
285 $games_css = OPENSTATION_DIR . 'assets/css/games.css';
286 wp_register_style(
287 'desktop-mode-games',
288 OPENSTATION_URL . 'assets/css/games.css',
289 array( 'os-variables', 'dashicons' ),
290 file_exists( $games_css ) ? (string) filemtime( $games_css ) : $version
291 );
292 $game_inkfall_css = OPENSTATION_DIR . 'assets/css/game-inkfall.css';
293 wp_register_style(
294 'os-game-inkfall',
295 OPENSTATION_URL . 'assets/css/game-inkfall.css',
296 array( 'os-variables' ),
297 file_exists( $game_inkfall_css ) ? (string) filemtime( $game_inkfall_css ) : $version
298 );
299 $game_alphabet_soup_css = OPENSTATION_DIR . 'assets/css/game-alphabet-soup.css';
300 wp_register_style(
301 'os-game-alphabet-soup',
302 OPENSTATION_URL . 'assets/css/game-alphabet-soup.css',
303 array( 'os-variables' ),
304 file_exists( $game_alphabet_soup_css ) ? (string) filemtime( $game_alphabet_soup_css ) : $version
305 );
306
307 // Pinned-notes layer styles (paper, pushpin, pastel tokens, pin
308 // animations). Same `filemtime` cache-bust posture as the other
309 // fast-iterating feature stylesheets above.
310 $notes_css = OPENSTATION_DIR . 'assets/css/notes.css';
311 wp_register_style(
312 'os-notes',
313 OPENSTATION_URL . 'assets/css/notes.css',
314 array( 'os-variables', 'dashicons' ),
315 file_exists( $notes_css ) ? (string) filemtime( $notes_css ) : $version
316 );
317
318 // Announcement-dialog styles (scrim, hero card, buttons). Registered
319 // always, enqueued only for a user who is actually owed the
320 // announcement — see the gate in `openstation_enqueue_assets()`. It
321 // is not lazy-loaded on top of that: on the one boot where it does
322 // go out, the dialog opens from the main bundle a second later, and
323 // a fetch racing that would show an unstyled card.
324 $announce_css = OPENSTATION_DIR . 'assets/css/announce.css';
325 wp_register_style(
326 'os-announce',
327 OPENSTATION_URL . 'assets/css/announce.css',
328 array( 'os-variables' ),
329 file_exists( $announce_css ) ? (string) filemtime( $announce_css ) : $version
330 );
331
332 // Scripts.
333 //
334 // `wp-hooks` — the shell exposes a WordPress-style filter/action
335 // API (`window.wp.hooks`) to third-party plugins.
336 // `wp-i18n` — the TS `__()` / `_x()` / `sprintf()` wrappers in
337 // `src/i18n.ts` delegate to `window.wp.i18n` for translation
338 // lookups. Both handles are core-shipped but only pre-enqueued
339 // when Gutenberg-adjacent deps pull them in, so we list them
340 // explicitly to guarantee load order.
341 wp_register_script(
342 'openstation',
343 OPENSTATION_URL . 'assets/js/desktop' . $suffix . '.js',
344 // `heartbeat` + `jquery` — the recycle-bin badge module
345 // (loaded as part of this bundle) opts into the WordPress
346 // Heartbeat API so the count tile / desktop-icon badge
347 // stays in sync even when the bin window is closed.
348 array( 'wp-hooks', 'wp-i18n', 'heartbeat', 'jquery' ),
349 $built_version( 'assets/js/desktop' . $suffix . '.js' ),
350 // Footer + defer: the shell boots on DOMContentLoaded anyway,
351 // so deferring frees the parser instead of blocking at the
352 // footer print point. Both inline payloads attached to this
353 // handle (`__openStationMenuCommands`, the jazz-quote version
354 // stamp) are `'before'`-position, which WP keeps as blocking
355 // inline ahead of a deferred tag — order is preserved. On WP
356 // < 6.3 the array collapses to a truthy `$in_footer`, same as
357 // before.
358 array(
359 'in_footer' => true,
360 'strategy' => 'defer',
361 )
362 );
363
364 // `os-iframe-bridge` — opt-in iframe-side bridge that
365 // provides `wp.os.iframe.publish/subscribe/onConnection/
366 // requestConnection` to any same-origin iframe that enqueues it.
367 // Same code is also injected inline by the chromeless bridge
368 // (so chromeless wp-admin pages don't need a separate enqueue)
369 // and auto-injected when a native window opts in via
370 // `iframeContent: { bridge: true }`. Plugins targeting their
371 // own iframe pages just enqueue this handle.
372 wp_register_script(
373 'os-iframe-bridge',
374 OPENSTATION_URL . 'assets/js/iframe-bridge' . $suffix . '.js',
375 array(),
376 $built_version( 'assets/js/iframe-bridge' . $suffix . '.js' ),
377 true
378 );
379
380 // `os-gutenberg-drop-receiver` — iframe-side bundle
381 // enqueued only on the Block Editor screens (`post.php` /
382 // `post-new.php`) for openstation users. Listens for
383 // `os-drop` postMessages from the parent shell (see
384 // `src/drag/iframe-drop-targets.ts`) and inserts the matching
385 // Gutenberg block via `wp.data.dispatch('core/block-editor')`.
386 // Depends on `wp-blocks` + `wp-data` so the editor stores are
387 // guaranteed enqueued before the receiver runs its first message
388 // handler.
389 wp_register_script(
390 'os-gutenberg-drop-receiver',
391 OPENSTATION_URL . 'assets/js/gutenberg-drop-receiver' . $suffix . '.js',
392 array( 'wp-blocks', 'wp-data' ),
393 $built_version( 'assets/js/gutenberg-drop-receiver' . $suffix . '.js' ),
394 true
395 );
396
397 // `desktop-mode-recycle-bin` — small bundle for the Recycle Bin
398 // native window. Lazy-loaded by the native-window sync the first
399 // time the bin opens; registers a render callback on
400 // `window.openStationNativeWindows['desktop-mode-recycle-bin']`.
401 $recycle_bin_js = OPENSTATION_DIR . 'assets/js/recycle-bin' . $suffix . '.js';
402 wp_register_script(
403 'desktop-mode-recycle-bin',
404 OPENSTATION_URL . 'assets/js/recycle-bin' . $suffix . '.js',
405 // `heartbeat` + `jquery` — the bin opts in to the WordPress
406 // Heartbeat API while its window is open as the catch-all
407 // real-time channel for deletes that don't render an admin
408 // footer (REST/AJAX/other tabs/WP-CLI). See
409 // `src/recycle-bin/realtime.ts` for the subscriber.
410 array( 'wp-i18n', 'heartbeat', 'jquery' ),
411 file_exists( $recycle_bin_js ) ? (string) filemtime( $recycle_bin_js ) : $version,
412 true
413 );
414 wp_set_script_translations(
415 'desktop-mode-recycle-bin',
416 'desktop-mode',
417 OPENSTATION_DIR . 'languages'
418 );
419
420 // `desktop-mode-games` — bundle for the Games hub native window
421 // (launcher grid, scoreboard, challenges client). Lazy-loaded by
422 // the native-window sync the first time the hub opens; registers a
423 // render callback on
424 // `window.openStationNativeWindows['desktop-mode-games']`.
425 // `heartbeat` + `jquery` — the challenges client rides the
426 // WordPress Heartbeat bus for live delivery.
427 $games_js = OPENSTATION_DIR . 'assets/js/games' . $suffix . '.js';
428 wp_register_script(
429 'desktop-mode-games',
430 OPENSTATION_URL . 'assets/js/games' . $suffix . '.js',
431 array( 'wp-i18n', 'heartbeat', 'jquery' ),
432 file_exists( $games_js ) ? (string) filemtime( $games_js ) : $version,
433 true
434 );
435 wp_set_script_translations(
436 'desktop-mode-games',
437 'desktop-mode',
438 OPENSTATION_DIR . 'languages'
439 );
440
441 // `os-game-inkfall` — the Inkfall game bundle. Loaded
442 // lazily by the games framework on first launch; publishes the
443 // game def on `window.openStationGames.inkfall`.
444 $game_inkfall_js = OPENSTATION_DIR . 'assets/js/game-inkfall' . $suffix . '.js';
445 wp_register_script(
446 'os-game-inkfall',
447 OPENSTATION_URL . 'assets/js/game-inkfall' . $suffix . '.js',
448 array( 'wp-i18n' ),
449 file_exists( $game_inkfall_js ) ? (string) filemtime( $game_inkfall_js ) : $version,
450 true
451 );
452 wp_set_script_translations(
453 'os-game-inkfall',
454 'desktop-mode',
455 OPENSTATION_DIR . 'languages'
456 );
457
458 // `os-game-alphabet-soup` — the Alphabet Soup game
459 // bundle. Loaded lazily by the games framework on first launch;
460 // publishes the game def on
461 // `window.openStationGames['alphabet-soup']`.
462 $game_alphabet_soup_js = OPENSTATION_DIR . 'assets/js/game-alphabet-soup' . $suffix . '.js';
463 wp_register_script(
464 'os-game-alphabet-soup',
465 OPENSTATION_URL . 'assets/js/game-alphabet-soup' . $suffix . '.js',
466 array( 'wp-i18n' ),
467 file_exists( $game_alphabet_soup_js ) ? (string) filemtime( $game_alphabet_soup_js ) : $version,
468 true
469 );
470 wp_set_script_translations(
471 'os-game-alphabet-soup',
472 'desktop-mode',
473 OPENSTATION_DIR . 'languages'
474 );
475
476 // `os-posts-window` — small bundle for the native Posts
477 // window. Lazy-loaded by the native-window sync the first time the
478 // window opens (via the dock-click swap when the user opts in);
479 // registers a render callback on
480 // `window.openStationNativeWindows['desktop-mode-posts']`.
481 $posts_window_js = OPENSTATION_DIR . 'assets/js/posts-window' . $suffix . '.js';
482 wp_register_script(
483 'os-posts-window',
484 OPENSTATION_URL . 'assets/js/posts-window' . $suffix . '.js',
485 array( 'wp-i18n' ),
486 file_exists( $posts_window_js ) ? (string) filemtime( $posts_window_js ) : $version,
487 true
488 );
489 wp_set_script_translations(
490 'os-posts-window',
491 'desktop-mode',
492 OPENSTATION_DIR . 'languages'
493 );
494
495 // `os-plugins-window` — small bundle for the native
496 // Plugins window. Lazy-loaded by the native-window sync the first
497 // time the window opens (via the dock-click swap when the user
498 // opts in); registers a render callback on
499 // `window.openStationNativeWindows['desktop-mode-plugins']`.
500 $plugins_window_js = OPENSTATION_DIR . 'assets/js/plugins-window' . $suffix . '.js';
501 wp_register_script(
502 'os-plugins-window',
503 OPENSTATION_URL . 'assets/js/plugins-window' . $suffix . '.js',
504 array( 'wp-i18n' ),
505 file_exists( $plugins_window_js ) ? (string) filemtime( $plugins_window_js ) : $version,
506 true
507 );
508 wp_set_script_translations(
509 'os-plugins-window',
510 'desktop-mode',
511 OPENSTATION_DIR . 'languages'
512 );
513
514 // `os-comments-window` — small bundle for the native
515 // Comments window. Lazy-loaded by the native-window sync the first
516 // time the window opens (via the dock-click swap when the user
517 // opts in); registers a render callback on
518 // `window.openStationNativeWindows['desktop-mode-comments']`.
519 $comments_window_js = OPENSTATION_DIR . 'assets/js/comments-window' . $suffix . '.js';
520 wp_register_script(
521 'os-comments-window',
522 OPENSTATION_URL . 'assets/js/comments-window' . $suffix . '.js',
523 array( 'wp-i18n' ),
524 file_exists( $comments_window_js ) ? (string) filemtime( $comments_window_js ) : $version,
525 true
526 );
527 wp_set_script_translations(
528 'os-comments-window',
529 'desktop-mode',
530 OPENSTATION_DIR . 'languages'
531 );
532
533 // `os-animated-logo-wallpaper` — built-in PixiJS canvas
534 // wallpaper, moved out of `desktop.min.js`. The wallpaper
535 // `server-sync` loads this handle when the user selects the
536 // `wp-animated-logo` wallpaper (or opens OS Settings → Wallpaper
537 // and the picker pulls every registered canvas def in). The
538 // bundle's only side effect is publishing the `WallpaperDef` on
539 // `window.openStationWallpapers['wp-animated-logo']`.
540 $animated_logo_js = OPENSTATION_DIR . 'assets/js/animated-logo-wallpaper' . $suffix . '.js';
541 wp_register_script(
542 'os-animated-logo-wallpaper',
543 OPENSTATION_URL . 'assets/js/animated-logo-wallpaper' . $suffix . '.js',
544 array( 'wp-hooks' ),
545 file_exists( $animated_logo_js ) ? (string) filemtime( $animated_logo_js ) : $version,
546 true
547 );
548
549 // `os-snow-wallpaper` — built-in PixiJS canvas
550 // wallpaper: snowfall that accumulates on window tops and melts
551 // away. Same lazy-load path as the animated logo: the wallpaper
552 // `server-sync` injects this handle when the user selects the
553 // `wp-snow` wallpaper (or opens OS Settings → Wallpaper and the
554 // picker pulls the def in). The bundle's only side effect is
555 // publishing the `WallpaperDef` on
556 // `window.openStationWallpapers['wp-snow']`.
557 $snow_js = OPENSTATION_DIR . 'assets/js/snow-wallpaper' . $suffix . '.js';
558 wp_register_script(
559 'os-snow-wallpaper',
560 OPENSTATION_URL . 'assets/js/snow-wallpaper' . $suffix . '.js',
561 array( 'wp-hooks', 'wp-i18n' ),
562 file_exists( $snow_js ) ? (string) filemtime( $snow_js ) : $version,
563 true
564 );
565 wp_set_script_translations(
566 'os-snow-wallpaper',
567 'desktop-mode',
568 OPENSTATION_DIR . 'languages'
569 );
570
571 // `desktop-mode-ai-assistant` — AI Copilot spotlight overlay,
572 // moved out of `desktop.min.js`. The main bundle ships a
573 // stub matching the public `wp.os.ai` contract; the stub
574 // `<script>`-injects this handle the first time the user opens
575 // the assistant (Cmd+K or admin-bar button).
576 $ai_assistant_js = OPENSTATION_DIR . 'assets/js/ai-assistant' . $suffix . '.js';
577 wp_register_script(
578 'desktop-mode-ai-assistant',
579 OPENSTATION_URL . 'assets/js/ai-assistant' . $suffix . '.js',
580 array( 'wp-hooks', 'wp-i18n' ),
581 file_exists( $ai_assistant_js ) ? (string) filemtime( $ai_assistant_js ) : $version,
582 true
583 );
584 wp_set_script_translations(
585 'desktop-mode-ai-assistant',
586 'desktop-mode',
587 OPENSTATION_DIR . 'languages'
588 );
589
590 // Wire the translation bundle to this script handle. WP looks
591 // for `languages/os-{locale}-os.json` and
592 // injects its `locale_data` into `wp.i18n` just before the
593 // script runs — so every `__()` call resolves to the right
594 // language without any runtime fetch.
595 wp_set_script_translations(
596 'openstation',
597 'desktop-mode',
598 OPENSTATION_DIR . 'languages'
599 );
600 }
601 add_action( 'init', 'openstation_register_assets' );
602