diff --git a/docs/plugin-panes.md b/docs/plugin-panes.md index fe28bda..4cfb6b4 100644 --- a/docs/plugin-panes.md +++ b/docs/plugin-panes.md @@ -1,249 +1,147 @@ # Detachable panes (`window.feedBack.panes`) -A **pane** is live UI that stays put: a mixer, a camera rig, a readout, a settings -board. You author it once, and the host decides where it lives β€” docked beside the -player, or popped out into its own OS window that remembers where you put it and -minimizes to the system tray. +Pop a panel out of the app into its own OS window, and leave it there: while you +play, across song switches, on a second monitor, minimized to the system tray. -Panes exist because the player's rail popovers are **exclusive**: opening one closes -the last. You cannot watch the mixer while riding the camera, and both vanish the -moment you want to look at the highway. Panes are non-exclusive, and they survive -song switches. +Panes exist because the player's rail popovers are **exclusive** β€” opening one +closes the last. You cannot watch the mixer while riding the camera, and both +vanish the moment you want to look at the highway. --- -## The two-line version +## The whole idea, in one sentence -If your plugin already has a dialog, give it a pop-out chip: +**We move the real element.** + +Not a copy of your panel. Not a re-implementation of it in the pop-out window. +The actual DOM node. Same-origin windows can adopt each other's nodes, and an +adopted node keeps its event listeners and its closures β€” so your panel goes on +running *your* code, against *your* state, in *your* realm. The app's stylesheets +are copied into the pane window, so it looks identical too. + +What you popped out is what you get. That is the promise, and it is the reason +there is no `ctx`, no state mirroring, no cross-window RPC and no second copy of +your UI to keep in step with the first. Those are all solutions to a problem we +simply do not have. + +--- + +## Adding a pane to your plugin + +Two lines. ```js feedBack.panes.register({ id: 'camera_director', title: 'Camera Director', icon: 'πŸŽ₯', - mount(root, ctx) { root.appendChild(buildCameraUI(ctx)); }, // your existing builder - unmount(root) { root.replaceChildren(); }, + element: () => panelEl, // your existing panel, as it is }); -feedBack.panes.attachChip(myDialogEl, 'camera_director'); +feedBack.panes.attachChip(panelEl, 'camera_director'); ``` -`attachChip()` injects **the** standard ⇱ button β€” same glyph, same place, same -behaviour everywhere. Clicking it opens the pane in its host and **hides your -dialog**, leaving a "⇲ … is popped out" stub in its place. Closing the pane -un-hides your dialog and restores the chip. +`attachChip()` injects **the** standard pop-out chip (`⇱`) β€” same glyph, same +place, same behaviour in every plugin. Clicking it moves your panel into a window +and leaves a "⇲ … is popped out" stub in its place; clicking the stub brings it +back, to exactly the spot it left. Core owns the chip, the hiding and the stub, so +you write no show/hide logic. -**You write no show/hide logic.** Core owns it, so that every plugin's pop-out -behaves identically β€” which is the entire point. +That's it. Your sliders, your presets, your tabs, your CSS, your event handlers, +your state β€” all of it comes along, because none of it moved anywhere except into +a different window's document. ---- +### `element` is a function for a reason -## The one rule +It is resolved at open time, not at registration. Plugins commonly build their +panel lazily on first use, or rebuild it wholesale when something changes (Camera +Director rebuilds its panel on every mode change). Asking for it when we need it +means we always move the live one. -> **`mount(root, ctx)` runs in a realm that may not have the app in it.** +### The one thing core changes about your element -Docked, your pane runs in the main window with everything present. Popped out, it -runs in a **separate JS realm** β€” a different window, with no `window.highway`, no -`window.feedBack.capabilities`, no `