PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.13
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.13
1.1.13 1.1.12 1.1.11 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 All 37 releases
desktop-mode / includes / assets.php

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

590 lines 23.3 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` is deliberately NOT in this chain — it is
127 * registered below, after `os-windows`, so it can load deferred.
128 * Adding a sheet here means splicing it into the chain, not
129 * appending an unrelated dependency: order is the contract.
130 * (The Preferences window's sheet is not here at all: it rides
131 * the `apps/os-settings/` app as a first-open companion style.)
132 */
133 $window_sheets = array(
134 'os-window-chrome' => 'assets/css/window-chrome.css',
135 'os-window-states' => 'assets/css/window-states.css',
136 'os-effects' => 'assets/css/effects.css',
137 'os-window-links' => 'assets/css/window-links.css',
138 );
139 $previous = array( 'os-variables', 'dashicons' );
140 foreach ( $window_sheets as $handle => $relative ) {
141 wp_register_style(
142 $handle,
143 OPENSTATION_URL . $relative,
144 $previous,
145 $built_version( $relative )
146 );
147 $previous = array( $handle );
148 }
149
150 // Entry point. Depends on the tail of the chain above, so
151 // enqueuing this one handle still pulls in every critical window
152 // sheet — the behaviour callers had when they were `@import`s.
153 wp_register_style(
154 'os-windows',
155 OPENSTATION_URL . 'assets/css/windows.css',
156 $previous,
157 $built_version( 'assets/css/windows.css' )
158 );
159 // Loads DEFERRED (see `openstation_defer_non_critical_styles()`):
160 // the UI it styles — the window overview — is lazy-loaded JS that
161 // can never be on screen at first paint, so its CSS has no
162 // business blocking render. It depends on `os-windows` so it
163 // prints after it, preserving the cascade position it had as an
164 // `@import`.
165 wp_register_style(
166 'os-window-overview',
167 OPENSTATION_URL . 'assets/css/window-overview.css',
168 array( 'os-windows' ),
169 $built_version( 'assets/css/window-overview.css' )
170 );
171 // Solo mode — one window, no desk around it. Loads last so it can
172 // hide surfaces the sheets above declared, and only enqueues on a
173 // solo request, so a normal shell never pays for it.
174 wp_register_style(
175 'os-solo',
176 OPENSTATION_URL . 'assets/css/solo.css',
177 array( 'os-windows' ),
178 $built_version( 'assets/css/solo.css' )
179 );
180 wp_register_style(
181 'os-dock',
182 OPENSTATION_URL . 'assets/css/dock.css',
183 array( 'os-variables', 'dashicons' ),
184 $built_version( 'assets/css/dock.css' )
185 );
186 // The Taskbar rail renderer. Scoped to the rail it stamps, so it is
187 // inert under the icon strip; loaded with the dock because the
188 // renderer can be picked live.
189 wp_register_style(
190 'os-dock-taskbar',
191 OPENSTATION_URL . 'assets/css/dock-taskbar.css',
192 array( 'os-dock' ),
193 $built_version( 'assets/css/dock-taskbar.css' )
194 );
195 wp_register_style(
196 'os-dock-peek',
197 OPENSTATION_URL . 'assets/css/dock-peek.css',
198 array( 'os-dock' ),
199 $built_version( 'assets/css/dock-peek.css' )
200 );
201 // The phone layer. Every rule is scoped to
202 // `html[data-os-mode="mobile"]`, so the sheet is inert on a
203 // desktop and the first paint on a phone is already right (the
204 // head stamp writes the attribute before any stylesheet applies).
205 // Depends on the dock and window sheets because it overrides them.
206 wp_register_style(
207 'os-mobile',
208 OPENSTATION_URL . 'assets/css/mobile.css',
209 array( 'os-variables', 'dashicons', 'os-dock', 'os-windows' ),
210 $built_version( 'assets/css/mobile.css' )
211 );
212 // The workspace wizard. Scoped to `.os-workspace-wizard`, so it is
213 // inert until the user opens it from the overview bar's `+` or a
214 // tile's Edit. Those controls are styled with the rest of the bar
215 // in `window-overview.css`.
216 wp_register_style(
217 'os-workspaces',
218 OPENSTATION_URL . 'assets/css/workspaces.css',
219 array( 'os-variables', 'dashicons' ),
220 $built_version( 'assets/css/workspaces.css' )
221 );
222 // Keyboard-shortcuts window. Scoped to `.os-shortcuts`, so it is
223 // inert until the System menu opens the window, and unconditional
224 // for the same reason the layout sheet is: the window can be
225 // opened at any moment and a deferred sheet would paint it raw.
226 wp_register_style(
227 'os-shortcuts',
228 OPENSTATION_URL . 'assets/css/shortcuts.css',
229 array( 'os-variables' ),
230 $built_version( 'assets/css/shortcuts.css' )
231 );
232 // The OpenStation desktop layout — the core/plugin seam on the rail
233 // plus the constellation hover-submenu flyout. Both surfaces are
234 // scoped (`[data-os-layout="openstation"]` / `.os-constellation`),
235 // so the sheet is inert in every other layout and is loaded
236 // unconditionally rather than gated on the user's current pick:
237 // the layout is switchable live from OpenStation Preferences, and a
238 // conditional enqueue would leave the first switch unstyled.
239 wp_register_style(
240 'os-openstation-layout',
241 OPENSTATION_URL . 'assets/css/openstation-layout.css',
242 array( 'os-dock' ),
243 $built_version( 'assets/css/openstation-layout.css' )
244 );
245 // `filemtime`-stamped — the chromeless overrides iterate faster
246 // than the plugin-wide version bumps (per-page compat shims and
247 // page-title-action exceptions land in patches), and a stale
248 // cached copy means the user sees yesterday's rules. Without the
249 // stamp, the browser keeps `?ver=0.8.1` valid for the whole
250 // release cycle.
251 wp_register_style(
252 'os-chromeless',
253 OPENSTATION_URL . 'assets/css/chromeless.css',
254 array( 'openstation' ),
255 $built_version( 'assets/css/chromeless.css' )
256 );
257
258 // `filemtime`-stamped — the AI assistant surface (error states,
259 // inline affordances) iterates faster than plugin version bumps.
260 // Without an mtime stamp the browser keeps `?ver=<plugin-version>`
261 // valid for the whole release cycle and the user keeps seeing
262 // yesterday's CSS even after a hard reload.
263 wp_register_style(
264 'desktop-mode-ai-assistant',
265 OPENSTATION_URL . 'assets/css/ai-assistant.css',
266 array( 'os-variables' ),
267 $built_version( 'assets/css/ai-assistant.css' )
268 );
269
270 wp_register_style(
271 'desktop-mode-bug-report',
272 OPENSTATION_URL . 'assets/css/bug-report.css',
273 array( 'os-variables' ),
274 $built_version( 'assets/css/bug-report.css' )
275 );
276
277 // `filemtime` instead of the plugin-wide `$version` for the
278 // recycle-bin CSS — this file iterates faster than the bundle
279 // and we never want a stale CSS cache to mask a real fix.
280 $recycle_bin_css = OPENSTATION_DIR . 'assets/css/recycle-bin.css';
281 wp_register_style(
282 'desktop-mode-recycle-bin',
283 OPENSTATION_URL . 'assets/css/recycle-bin.css',
284 array( 'os-variables', 'dashicons' ),
285 file_exists( $recycle_bin_css ) ? (string) filemtime( $recycle_bin_css ) : $version
286 );
287
288 // Files-on-the-Desktop tile + layer styles. `filemtime` for the
289 // same reason as the recycle-bin CSS: this file
290 // iterates faster than the plugin version, and a stale cache
291 // would mask a real fix.
292 $desktop_files_css = OPENSTATION_DIR . 'assets/css/desktop-files.css';
293 wp_register_style(
294 'os-files',
295 OPENSTATION_URL . 'assets/css/desktop-files.css',
296 array( 'os-variables', 'dashicons' ),
297 file_exists( $desktop_files_css ) ? (string) filemtime( $desktop_files_css ) : $version
298 );
299
300 // Games hub window (launcher grid, scoreboard, challenges) +
301 // per-game styles. Same `filemtime` cache-bust posture as the
302 // other fast-iterating feature stylesheets.
303 $games_css = OPENSTATION_DIR . 'assets/css/games.css';
304 wp_register_style(
305 'desktop-mode-games',
306 OPENSTATION_URL . 'assets/css/games.css',
307 array( 'os-variables', 'dashicons' ),
308 file_exists( $games_css ) ? (string) filemtime( $games_css ) : $version
309 );
310 $game_inkfall_css = OPENSTATION_DIR . 'assets/css/game-inkfall.css';
311 wp_register_style(
312 'os-game-inkfall',
313 OPENSTATION_URL . 'assets/css/game-inkfall.css',
314 array( 'os-variables' ),
315 file_exists( $game_inkfall_css ) ? (string) filemtime( $game_inkfall_css ) : $version
316 );
317 $game_alphabet_soup_css = OPENSTATION_DIR . 'assets/css/game-alphabet-soup.css';
318 wp_register_style(
319 'os-game-alphabet-soup',
320 OPENSTATION_URL . 'assets/css/game-alphabet-soup.css',
321 array( 'os-variables' ),
322 file_exists( $game_alphabet_soup_css ) ? (string) filemtime( $game_alphabet_soup_css ) : $version
323 );
324
325 // Pinned-notes layer styles (paper, pushpin, pastel tokens, pin
326 // animations). Same `filemtime` cache-bust posture as the other
327 // fast-iterating feature stylesheets above. Depends on `os-files`
328 // because a pinned note dresses the canonical `.os-file-tile`
329 // chrome — anything that restyles a tile has to print after the
330 // file that declares one.
331 $notes_css = OPENSTATION_DIR . 'assets/css/notes.css';
332 wp_register_style(
333 'os-notes',
334 OPENSTATION_URL . 'assets/css/notes.css',
335 array( 'os-variables', 'dashicons', 'os-files' ),
336 file_exists( $notes_css ) ? (string) filemtime( $notes_css ) : $version
337 );
338
339 // Announcement-dialog styles (scrim, hero card, buttons). Registered
340 // always, enqueued only for a user who is actually owed the
341 // announcement — see the gate in `openstation_enqueue_assets()`. It
342 // is not lazy-loaded on top of that: on the one boot where it does
343 // go out, the dialog opens from the main bundle a second later, and
344 // a fetch racing that would show an unstyled card.
345 $announce_css = OPENSTATION_DIR . 'assets/css/announce.css';
346 wp_register_style(
347 'os-announce',
348 OPENSTATION_URL . 'assets/css/announce.css',
349 array( 'os-variables' ),
350 file_exists( $announce_css ) ? (string) filemtime( $announce_css ) : $version
351 );
352
353 // Scripts.
354 //
355 // `wp-hooks` — the shell exposes a WordPress-style filter/action
356 // API (`window.wp.hooks`) to third-party plugins.
357 // `wp-i18n` — the TS `__()` / `_x()` / `sprintf()` wrappers in
358 // `src/i18n.ts` delegate to `window.wp.i18n` for translation
359 // lookups. Both handles are core-shipped but only pre-enqueued
360 // when Gutenberg-adjacent deps pull them in, so we list them
361 // explicitly to guarantee load order.
362 wp_register_script(
363 'openstation',
364 OPENSTATION_URL . 'assets/js/desktop' . $suffix . '.js',
365 // `heartbeat` + `jquery` — the recycle-bin badge module
366 // (loaded as part of this bundle) opts into the WordPress
367 // Heartbeat API so the count tile / desktop-icon badge
368 // stays in sync even when the bin window is closed.
369 array( 'wp-hooks', 'wp-i18n', 'heartbeat', 'jquery' ),
370 $built_version( 'assets/js/desktop' . $suffix . '.js' ),
371 // Footer + defer: the shell boots on DOMContentLoaded anyway,
372 // so deferring frees the parser instead of blocking at the
373 // footer print point. Both inline payloads attached to this
374 // handle (`__openStationMenuCommands`, the jazz-quote version
375 // stamp) are `'before'`-position, which WP keeps as blocking
376 // inline ahead of a deferred tag — order is preserved. On WP
377 // < 6.3 the array collapses to a truthy `$in_footer`, same as
378 // before.
379 array(
380 'in_footer' => true,
381 'strategy' => 'defer',
382 )
383 );
384
385 // `os-iframe-bridge` — opt-in iframe-side bridge that
386 // provides `wp.os.iframe.publish/subscribe/onConnection/
387 // requestConnection` to any same-origin iframe that enqueues it.
388 // Same code is also injected inline by the chromeless bridge
389 // (so chromeless wp-admin pages don't need a separate enqueue)
390 // and auto-injected when a native window opts in via
391 // `iframeContent: { bridge: true }`. Plugins targeting their
392 // own iframe pages just enqueue this handle.
393 wp_register_script(
394 'os-iframe-bridge',
395 OPENSTATION_URL . 'assets/js/iframe-bridge' . $suffix . '.js',
396 array(),
397 $built_version( 'assets/js/iframe-bridge' . $suffix . '.js' ),
398 true
399 );
400
401 // `os-deactivation-feedback` — the dialog that asks one optional
402 // question when an admin deactivates OpenStation. Enqueued on
403 // `plugins.php` (classic, chromeless and network admin) by
404 // `includes/feedback/deactivation.php`, and lazy-loaded by the
405 // native Plugins app from its config block. Plain DOM on purpose:
406 // the classic screen has no `<os-*>` kit.
407 wp_register_script(
408 'os-deactivation-feedback',
409 OPENSTATION_URL . 'assets/js/deactivation-feedback' . $suffix . '.js',
410 array( 'wp-i18n' ),
411 $built_version( 'assets/js/deactivation-feedback' . $suffix . '.js' ),
412 true
413 );
414 wp_set_script_translations(
415 'os-deactivation-feedback',
416 'desktop-mode',
417 OPENSTATION_DIR . 'languages'
418 );
419 $feedback_css = OPENSTATION_DIR . 'assets/css/deactivation-feedback.css';
420 wp_register_style(
421 'os-deactivation-feedback',
422 OPENSTATION_URL . 'assets/css/deactivation-feedback.css',
423 array(),
424 file_exists( $feedback_css ) ? (string) filemtime( $feedback_css ) : $version
425 );
426
427 // `os-chromeless-bridge` — the iframe side of every window,
428 // enqueued on `admin_footer` by
429 // `openstation_chromeless_bridge_script()` with its per-request
430 // data attached as a `before` inline block. Registered here (not
431 // there) so the handle exists before the enqueue and so the asset
432 // guard's OPENSTATION_URL snapshot protects it like every other
433 // bundle of ours.
434 wp_register_script(
435 'os-chromeless-bridge',
436 OPENSTATION_URL . 'assets/js/chromeless-bridge' . $suffix . '.js',
437 array(),
438 $built_version( 'assets/js/chromeless-bridge' . $suffix . '.js' ),
439 true
440 );
441
442 // `os-gutenberg-drop-receiver` — iframe-side bundle
443 // enqueued only on the Block Editor screens (`post.php` /
444 // `post-new.php`) for openstation users. Listens for
445 // `os-drop` postMessages from the parent shell (see
446 // `src/drag/iframe-drop-targets.ts`) and inserts the matching
447 // Gutenberg block via `wp.data.dispatch('core/block-editor')`.
448 // Depends on `wp-blocks` + `wp-data` so the editor stores are
449 // guaranteed enqueued before the receiver runs its first message
450 // handler.
451 wp_register_script(
452 'os-gutenberg-drop-receiver',
453 OPENSTATION_URL . 'assets/js/gutenberg-drop-receiver' . $suffix . '.js',
454 array( 'wp-blocks', 'wp-data' ),
455 $built_version( 'assets/js/gutenberg-drop-receiver' . $suffix . '.js' ),
456 true
457 );
458
459 // The Recycle Bin window is an App Framework app (`apps/trash/`);
460 // its client view rides the app-bundle pipeline, so no dedicated
461 // script handle. The `desktop-mode-recycle-bin` STYLE handle above
462 // stays — the drag-to-trash drop-target highlight must be present
463 // at boot (dropping on the closed bin's dock tile), not window-open.
464
465 // `desktop-mode-games` — bundle for the Games hub native window
466 // (launcher grid, scoreboard, challenges client). Lazy-loaded by
467 // the native-window sync the first time the hub opens; registers a
468 // render callback on
469 // `window.openStationNativeWindows['desktop-mode-games']`.
470 // `heartbeat` + `jquery` — the challenges client rides the
471 // WordPress Heartbeat bus for live delivery.
472 $games_js = OPENSTATION_DIR . 'assets/js/games' . $suffix . '.js';
473 wp_register_script(
474 'desktop-mode-games',
475 OPENSTATION_URL . 'assets/js/games' . $suffix . '.js',
476 array( 'wp-i18n', 'heartbeat', 'jquery' ),
477 file_exists( $games_js ) ? (string) filemtime( $games_js ) : $version,
478 true
479 );
480 wp_set_script_translations(
481 'desktop-mode-games',
482 'desktop-mode',
483 OPENSTATION_DIR . 'languages'
484 );
485
486 // `os-game-inkfall` — the Inkfall game bundle. Loaded
487 // lazily by the games framework on first launch; publishes the
488 // game def on `window.openStationGames.inkfall`.
489 $game_inkfall_js = OPENSTATION_DIR . 'assets/js/game-inkfall' . $suffix . '.js';
490 wp_register_script(
491 'os-game-inkfall',
492 OPENSTATION_URL . 'assets/js/game-inkfall' . $suffix . '.js',
493 array( 'wp-i18n' ),
494 file_exists( $game_inkfall_js ) ? (string) filemtime( $game_inkfall_js ) : $version,
495 true
496 );
497 wp_set_script_translations(
498 'os-game-inkfall',
499 'desktop-mode',
500 OPENSTATION_DIR . 'languages'
501 );
502
503 // `os-game-alphabet-soup` — the Alphabet Soup game
504 // bundle. Loaded lazily by the games framework on first launch;
505 // publishes the game def on
506 // `window.openStationGames['alphabet-soup']`.
507 $game_alphabet_soup_js = OPENSTATION_DIR . 'assets/js/game-alphabet-soup' . $suffix . '.js';
508 wp_register_script(
509 'os-game-alphabet-soup',
510 OPENSTATION_URL . 'assets/js/game-alphabet-soup' . $suffix . '.js',
511 array( 'wp-i18n' ),
512 file_exists( $game_alphabet_soup_js ) ? (string) filemtime( $game_alphabet_soup_js ) : $version,
513 true
514 );
515 wp_set_script_translations(
516 'os-game-alphabet-soup',
517 'desktop-mode',
518 OPENSTATION_DIR . 'languages'
519 );
520
521 // `os-animated-logo-wallpaper` — built-in PixiJS canvas
522 // wallpaper, moved out of `desktop.min.js`. The wallpaper
523 // `server-sync` loads this handle when the user selects the
524 // `wp-animated-logo` wallpaper (or opens OS Settings → Wallpaper
525 // and the picker pulls every registered canvas def in). The
526 // bundle's only side effect is publishing the `WallpaperDef` on
527 // `window.openStationWallpapers['wp-animated-logo']`.
528 $animated_logo_js = OPENSTATION_DIR . 'assets/js/animated-logo-wallpaper' . $suffix . '.js';
529 wp_register_script(
530 'os-animated-logo-wallpaper',
531 OPENSTATION_URL . 'assets/js/animated-logo-wallpaper' . $suffix . '.js',
532 array( 'wp-hooks' ),
533 file_exists( $animated_logo_js ) ? (string) filemtime( $animated_logo_js ) : $version,
534 true
535 );
536
537 // `os-snow-wallpaper` — built-in PixiJS canvas
538 // wallpaper: snowfall that accumulates on window tops and melts
539 // away. Same lazy-load path as the animated logo: the wallpaper
540 // `server-sync` injects this handle when the user selects the
541 // `wp-snow` wallpaper (or opens OS Settings → Wallpaper and the
542 // picker pulls the def in). The bundle's only side effect is
543 // publishing the `WallpaperDef` on
544 // `window.openStationWallpapers['wp-snow']`.
545 $snow_js = OPENSTATION_DIR . 'assets/js/snow-wallpaper' . $suffix . '.js';
546 wp_register_script(
547 'os-snow-wallpaper',
548 OPENSTATION_URL . 'assets/js/snow-wallpaper' . $suffix . '.js',
549 array( 'wp-hooks', 'wp-i18n' ),
550 file_exists( $snow_js ) ? (string) filemtime( $snow_js ) : $version,
551 true
552 );
553 wp_set_script_translations(
554 'os-snow-wallpaper',
555 'desktop-mode',
556 OPENSTATION_DIR . 'languages'
557 );
558
559 // `desktop-mode-ai-assistant` — AI Copilot spotlight overlay,
560 // moved out of `desktop.min.js`. The main bundle ships a
561 // stub matching the public `wp.os.ai` contract; the stub
562 // `<script>`-injects this handle the first time the user opens
563 // the assistant (Cmd+K or admin-bar button).
564 $ai_assistant_js = OPENSTATION_DIR . 'assets/js/ai-assistant' . $suffix . '.js';
565 wp_register_script(
566 'desktop-mode-ai-assistant',
567 OPENSTATION_URL . 'assets/js/ai-assistant' . $suffix . '.js',
568 array( 'wp-hooks', 'wp-i18n' ),
569 file_exists( $ai_assistant_js ) ? (string) filemtime( $ai_assistant_js ) : $version,
570 true
571 );
572 wp_set_script_translations(
573 'desktop-mode-ai-assistant',
574 'desktop-mode',
575 OPENSTATION_DIR . 'languages'
576 );
577
578 // Wire the translation bundle to this script handle. WP looks
579 // for `languages/os-{locale}-os.json` and
580 // injects its `locale_data` into `wp.i18n` just before the
581 // script runs — so every `__()` call resolves to the right
582 // language without any runtime fetch.
583 wp_set_script_translations(
584 'openstation',
585 'desktop-mode',
586 OPENSTATION_DIR . 'languages'
587 );
588 }
589 add_action( 'init', 'openstation_register_assets' );
590