officerdev/src/MusicPlayer/ → plugins/music/web/. Engine, state, bar,
favourites, lyrics toggle and the library vocabulary — ten files. The barrel
stops exporting a player it no longer has, and DashboardLayout stops rendering
one.
The reasoning that kept it was removed rather than refuted. It stayed because
the dashboard widget imported useMusicPlayer from officerdev and the platform
cannot import from a plugin, so the state had to stay whatever was decided
about the UI. The owner moved the widget into the plugin in the previous
commit, and the constraint went with it: the whole remaining dependency became
one line, DashboardLayout.tsx:66.
MusicPlayerHost is mounted inside the MusicDetail panel. That reads odd until
you notice it already returned null on /music — the mini bar is the transport
there, and the host existed purely to own the GaplessEngine. In the panel it
does exactly that, and the bar code stays intact for whenever there is a slot.
[phase 2] Leaving /music unmounts the host and playback stops. Deferred on the
owner's call; the bar was "navigating away must not break the application", and
that holds: seekPlayer is optional-chained so a call with no host registered is
a no-op, registerPlayerSeek clears only its own registration, the host's
cleanup destroys the engine and nulls its ref, and the queue is global state so
returning to /music remounts and reloads. Solving it properly needs either a
shell slot a plugin can contribute to — which reopens "there is no way to
export a component" — or the engine hoisted to module scope, which keeps the
rule and loses only the off-route controls.
Also: the parked widget now imports the player as a sibling rather than through
officerdev, and shared.ts stopped being a re-export shim now that the real file
is in the plugin.
Verified: tsgo clean, 797 tests / 787 pass / same 7. Server restarts, mounts
/example /music /offscale, / and /music both 200, and the player is in the
built bundle (music.volume, music:lyrics, now-playing?device=web all present —
GaplessEngine is a class name and the production build is minified, so grepping
for it proves nothing).
Not verified by me: what it looks like in a browser. That needs your eyes.
src/workspaces/widgets/MusicPlayer/ → plugins/music/widgets/, its export
dropped from the widgets package, and its registration removed from
WidgetRegistry. Plugins cannot contribute widgets and that mechanism is not
being invented now, so it is parked next to cliamp rather than left in a
workspace package the platform ships.
`../Widget` became `widgets/Widget` — the sibling import turned into that
package's declared export, which it already had.
This removes the last non-plugin consumer of officerdev/src/MusicPlayer, and
that matters more than the move. The argument for keeping the player in the
platform was that the widget imported useMusicPlayer and PlayerTrack from
officerdev, and the platform cannot import from a plugin — so the player STATE
had to stay whatever was decided about the UI, and the engine stayed with the
state.
That constraint is now gone. The complete remaining platform dependency on the
player is one line: DashboardLayout.tsx:66, `<MusicPlayerHost />`.
So the seam is no longer "the widget pins it". It is purely the global overlay
question, which is a platform gap about plugins owning a render slot outside
<Routes> — the same shape as plugins owning a websocket. Recorded rather than
acted on; the decision is the owner's and the previous reasoning for it no
longer holds.
bunx tsgo clean. 797 tests, 787 pass, same 7 pre-existing failures.