fluxplugin 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +15 -12
  2. package/dist/browser/chunks/chunk-AV4KI7LI.js +1792 -0
  3. package/dist/browser/chunks/{chunk-TL63576R.js → chunk-OAYWYU5O.js} +48 -11
  4. package/dist/browser/index.js +3 -1
  5. package/dist/browser/manager-ui.js +386 -185
  6. package/dist/browser/react.js +46 -2
  7. package/dist/cli.js +3622 -257
  8. package/dist/node/chunks/{chunk-EVX2O65A.js → chunk-SDCGUBEW.js} +49 -11
  9. package/dist/node/chunks/{chunk-C7VWZZOX.js → chunk-ZLA5XUXN.js} +175 -51
  10. package/dist/node/cli-api.js +742 -76
  11. package/dist/node/index.js +3 -1
  12. package/dist/node/main.js +8 -2
  13. package/dist/node/preload.js +1 -1
  14. package/dist/types/cli/build.d.ts +5 -0
  15. package/dist/types/cli/cli.d.ts +2 -0
  16. package/dist/types/cli/dist.d.ts +18 -0
  17. package/dist/types/cli/host-templates.d.ts +2 -0
  18. package/dist/types/cli/store.d.ts +13 -0
  19. package/dist/types/cli/templates.d.ts +2 -0
  20. package/dist/types/core/manifest.d.ts +15 -1
  21. package/dist/types/core/protocol.d.ts +18 -5
  22. package/dist/types/core/registry.d.ts +11 -0
  23. package/dist/types/core/routes.d.ts +3 -1
  24. package/dist/types/main/adapters.d.ts +3 -0
  25. package/dist/types/main/assets.d.ts +8 -0
  26. package/dist/types/main/github-registry.d.ts +2 -1
  27. package/dist/types/main/index.d.ts +1 -0
  28. package/dist/types/main/registry.d.ts +55 -18
  29. package/dist/types/main/stores.d.ts +7 -1
  30. package/dist/types/manager-ui/index.d.ts +35 -3
  31. package/dist/types/react/base-css.d.ts +6 -2
  32. package/dist/types/react/http-bridge.d.ts +5 -0
  33. package/dist/types/react/i18n.d.ts +33 -0
  34. package/dist/types/react/index.d.ts +5 -0
  35. package/dist/types/react/markdown.d.ts +2 -1
  36. package/dist/types/react/media.d.ts +33 -0
  37. package/dist/types/react/settings.d.ts +6 -0
  38. package/dist/types/react/sidebar.d.ts +42 -0
  39. package/dist/types/react/theme.d.ts +42 -4
  40. package/dist/types/react/ui.d.ts +42 -0
  41. package/docs/CUSTOMIZING.md +56 -0
  42. package/docs/STORE.md +19 -0
  43. package/docs/screenshots/plugin-page.png +0 -0
  44. package/docs/screenshots/settings-appearance.png +0 -0
  45. package/docs/screenshots/site-guide-dark.png +0 -0
  46. package/docs/screenshots/site-home.png +0 -0
  47. package/docs/screenshots/site-mobile.png +0 -0
  48. package/docs/screenshots/site-search.png +0 -0
  49. package/package.json +1 -1
  50. package/dist/browser/chunks/chunk-VXQJIDXW.js +0 -1232
@@ -1,1232 +0,0 @@
1
- import {
2
- MGMT,
3
- PermissionManager,
4
- PluginManager,
5
- errorMessage,
6
- normalizePath,
7
- s,
8
- scopeCss
9
- } from "./chunk-TL63576R.js";
10
-
11
- // src/react/runtime.ts
12
- var RemoteError = class extends Error {
13
- constructor(payload) {
14
- super(payload.message);
15
- this.payload = payload;
16
- this.name = "RemoteError";
17
- }
18
- payload;
19
- get code() {
20
- return this.payload.code;
21
- }
22
- };
23
- var PluginsClient = class {
24
- constructor(bridge) {
25
- this.bridge = bridge;
26
- }
27
- bridge;
28
- call(channel, input) {
29
- return this.bridge.invoke(channel, input);
30
- }
31
- list = () => this.call(MGMT.list);
32
- enable = (id) => this.call(MGMT.enable, { id });
33
- disable = (id) => this.call(MGMT.disable, { id });
34
- reload = (id) => this.call(MGMT.reload, { id });
35
- grant = (id, permission) => this.call(MGMT.grant, { id, permission });
36
- revoke = (id, permission) => this.call(MGMT.revoke, { id, permission });
37
- uninstall = (id) => this.call(MGMT.uninstall, { id });
38
- search = (query) => this.call(MGMT.search, { query });
39
- updates = () => this.call(MGMT.updates);
40
- install = (entry) => this.call(MGMT.install, { entry });
41
- update = (entry) => this.call(MGMT.update, { entry });
42
- rollback = (id, version) => this.call(MGMT.rollback, { id, version });
43
- configGet = (id) => this.call(MGMT.configGet, { id });
44
- configSet = (id, patch) => this.call(MGMT.configSet, { id, patch });
45
- configReset = (id) => this.call(MGMT.configReset, { id });
46
- configSchema = (id) => this.call(MGMT.configSchema, { id });
47
- logs = (id) => this.call(MGMT.logs, { id });
48
- details = (id) => this.call(MGMT.details, { id });
49
- diagnostics = (id) => this.call(MGMT.diagnostics, { id });
50
- };
51
- var asDefinition = (mod) => {
52
- const d = mod && typeof mod === "object" && "default" in mod ? mod.default : mod;
53
- if (!d || typeof d !== "object") throw new Error("renderer entry must default-export definePlugin({...})");
54
- return d;
55
- };
56
- var RendererFramework = class {
57
- constructor(opts) {
58
- this.opts = opts;
59
- this.bridge = opts.bridge;
60
- this.client = new PluginsClient(opts.bridge);
61
- this.manager = new PluginManager({
62
- runtime: "renderer",
63
- permissions: this.permissions,
64
- ipcBridge: {
65
- invoke: (name, input, caller) => opts.bridge.invoke(name, input, caller),
66
- send: (channel, payload, caller) => opts.bridge.send(channel, payload, caller),
67
- on: (channel, fn) => ({ dispose: opts.bridge.on(channel, fn) })
68
- },
69
- // Renderer plugins that crash must never take the app down; the main side persists auto-disable.
70
- ...opts.manager
71
- });
72
- this.manager.subscribe(() => this.touch());
73
- }
74
- opts;
75
- bridge;
76
- client;
77
- manager;
78
- permissions = new PermissionManager({ autoGrant: false });
79
- revision = -1;
80
- bootstrap;
81
- syncing = Promise.resolve();
82
- renderErrors = /* @__PURE__ */ new Map();
83
- unsubscribers = [];
84
- storeListeners = /* @__PURE__ */ new Set();
85
- _storeVersion = 0;
86
- /** `useSyncExternalStore` plumbing for the bootstrap-derived view (plugin list etc.). */
87
- get storeVersion() {
88
- return this._storeVersion + this.manager.version;
89
- }
90
- subscribe = (l) => {
91
- this.storeListeners.add(l);
92
- const off = this.manager.subscribe(l);
93
- return () => {
94
- this.storeListeners.delete(l);
95
- off();
96
- };
97
- };
98
- touch() {
99
- this._storeVersion++;
100
- for (const l of [...this.storeListeners]) l();
101
- }
102
- get currentBootstrap() {
103
- return this.bootstrap;
104
- }
105
- /** Fetches the bootstrap, activates enabled plugins and starts listening for changes. */
106
- async start() {
107
- await this.sync();
108
- this.unsubscribers.push(
109
- this.bridge.on(MGMT.changed, () => {
110
- void this.sync().catch((e) => console.error("[fluxplugin] sync failed", e));
111
- }),
112
- this.bridge.on("flux.notification", (n) => void this.manager.notifications.push(n, n?.owner ?? "main")),
113
- this.bridge.on("flux.event.plugin.hotreload", ({ id } = {}) => void this.hotReload(id))
114
- );
115
- }
116
- stop() {
117
- for (const u of this.unsubscribers.splice(0)) u();
118
- }
119
- hotReload(id) {
120
- this.cacheBust = Date.now();
121
- return this.sync(true).then(async () => {
122
- if (id && this.manager.has(id)) await this.manager.reload(id).catch(() => void 0);
123
- });
124
- }
125
- cacheBust = 0;
126
- /** Mirrors main-process state into the renderer manager. Calls are serialized. */
127
- sync(force = false) {
128
- const run = this.syncing.then(async () => {
129
- const boot = await this.bridge.bootstrap();
130
- if (!force && boot.revision === this.revision && this.bootstrap) return;
131
- this.revision = boot.revision;
132
- this.bootstrap = boot;
133
- await this.reconcile(boot);
134
- this.installWindowPlugins(boot.preload);
135
- this.touch();
136
- });
137
- this.syncing = run.catch(() => void 0);
138
- return run;
139
- }
140
- async reconcile(boot) {
141
- const seen = /* @__PURE__ */ new Set();
142
- for (const d of boot.plugins) {
143
- seen.add(d.id);
144
- this.permissions.restore({ [d.id]: d.granted });
145
- if (!this.manager.has(d.id)) {
146
- if (d.state === "error" && !d.rendererUrl) continue;
147
- this.manager.register({ manifest: d.manifest, origin: "renderer", load: () => this.load(d) });
148
- this.permissions.restore({ [d.id]: d.granted });
149
- } else {
150
- this.permissions.restore({ [d.id]: d.granted });
151
- }
152
- const info = this.manager.info(d.id);
153
- if (!d.enabled && info.enabled) await this.manager.disable(d.id).catch(() => void 0);
154
- else if (d.enabled && !info.enabled) await this.manager.enable(d.id, { activate: false }).catch(() => void 0);
155
- }
156
- for (const info of this.manager.list()) if (!seen.has(info.id)) await this.manager.unregister(info.id);
157
- await this.manager.startup();
158
- }
159
- async load(d) {
160
- let def = {};
161
- if (d.rendererUrl) {
162
- const url = this.cacheBust ? `${d.rendererUrl}${d.rendererUrl.includes("?") ? "&" : "?"}flux=${this.cacheBust}` : d.rendererUrl;
163
- const mod = await (this.opts.importPlugin ? this.opts.importPlugin(url, d) : import(
164
- /* @vite-ignore */
165
- url
166
- ));
167
- def = asDefinition(mod);
168
- }
169
- const hrefs = d.styleUrls;
170
- if (!hrefs.length) return def;
171
- const activate = def.onActivate ?? def.activate;
172
- return {
173
- ...def,
174
- onActivate: async (ctx) => {
175
- hrefs.forEach((href, i) => ctx.lifecycle.subscriptions.add(this.manager.styles.register({ id: `manifest-${i}`, href, priority: 50 }, ctx.pluginId)));
176
- await activate?.(ctx);
177
- },
178
- activate: void 0
179
- };
180
- }
181
- /** Called by plugin error boundaries. Repeated render failures disable the plugin. */
182
- reportRenderError(pluginId, error) {
183
- this.manager.logs.logger(pluginId).error(`render error: ${errorMessage(error)}`, error instanceof Error ? error.stack : void 0);
184
- const now = Date.now();
185
- const list = [...(this.renderErrors.get(pluginId) ?? []).filter((t) => now - t < 6e4), now];
186
- this.renderErrors.set(pluginId, list);
187
- if (list.length >= (this.opts.maxRenderErrors ?? 3) && this.manager.has(pluginId) && this.manager.info(pluginId).enabled) {
188
- this.manager.logs.logger(pluginId).warn("disabled automatically after repeated render errors");
189
- this.renderErrors.delete(pluginId);
190
- void this.client.disable(pluginId).catch(() => this.manager.disable(pluginId));
191
- }
192
- }
193
- /** Builds `window.plugins.<ns>.<member>` from the preload manifest. Rebuilt on every sync. */
194
- installWindowPlugins(spec) {
195
- if (typeof window === "undefined") return;
196
- window.plugins = buildPluginApis(this.bridge, spec);
197
- }
198
- /** Config handle usable from React (mirrors ctx.config through the management API). */
199
- configOf(id) {
200
- return {
201
- get: () => this.client.configGet(id),
202
- set: (p) => this.client.configSet(id, p),
203
- reset: () => this.client.configReset(id)
204
- };
205
- }
206
- };
207
- function buildPluginApis(bridge, spec) {
208
- const out = {};
209
- for (const [ns, members] of Object.entries(spec)) {
210
- const api = /* @__PURE__ */ Object.create(null);
211
- for (const [name, m] of Object.entries(members)) {
212
- if (m.kind === "invoke") api[name] = (input) => bridge.invoke(m.channel, input, ns);
213
- else if (m.kind === "send") api[name] = (payload) => bridge.send(m.channel, payload, ns);
214
- else api[name] = (listener) => bridge.on(m.channel, listener);
215
- }
216
- out[ns] = Object.freeze(api);
217
- }
218
- return Object.freeze(out);
219
- }
220
- function installSharedModules(modules) {
221
- const g = globalThis;
222
- g.__FLUXPLUGIN_SHARED__ = { ...g.__FLUXPLUGIN_SHARED__ ?? {}, ...modules };
223
- }
224
-
225
- // src/react/components.tsx
226
- import {
227
- Component,
228
- createContext,
229
- useCallback as useCallback2,
230
- useContext,
231
- useEffect as useEffect2,
232
- useMemo as useMemo2,
233
- useRef,
234
- useState as useState2,
235
- useSyncExternalStore as useSyncExternalStore2
236
- } from "react";
237
-
238
- // src/react/router.tsx
239
- import {
240
- Suspense,
241
- lazy,
242
- useCallback,
243
- useEffect,
244
- useMemo,
245
- useState,
246
- useSyncExternalStore
247
- } from "react";
248
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
249
- function currentPath() {
250
- if (typeof window === "undefined") return "/";
251
- const h = window.location.hash.replace(/^#/, "");
252
- return normalizePath(h || "/");
253
- }
254
- var listeners = /* @__PURE__ */ new Set();
255
- if (typeof window !== "undefined") window.addEventListener("hashchange", () => listeners.forEach((l) => l()));
256
- var subscribeLocation = (l) => (listeners.add(l), () => void listeners.delete(l));
257
- function navigate(to, opts = {}) {
258
- const target = "#" + normalizePath(to) + (to.includes("?") ? "?" + to.split("?")[1] : "");
259
- if (opts.replace) window.location.replace(target);
260
- else window.location.hash = target;
261
- listeners.forEach((l) => l());
262
- }
263
- function useLocation() {
264
- return useSyncExternalStore(subscribeLocation, currentPath, () => "/");
265
- }
266
- function useNavigate() {
267
- return navigate;
268
- }
269
- function Link({ to, children, ...rest }) {
270
- const here = useLocation();
271
- const active = here === normalizePath(to) || to !== "/" && here.startsWith(normalizePath(to) + "/");
272
- return /* @__PURE__ */ jsx("a", { ...rest, href: "#" + normalizePath(to), "data-active": active || void 0, "aria-current": active ? "page" : void 0, children });
273
- }
274
- function usePluginRoute() {
275
- const fw = usePluginFramework();
276
- const path = useLocation();
277
- const routes = fw.manager.routes;
278
- const version = useSyncExternalStore(routes.subscribe, () => routes.version, () => routes.version);
279
- return useMemo(() => {
280
- const match = routes.match(path);
281
- return { match, params: match?.params ?? {}, breadcrumbs: match ? routes.breadcrumbs(match) : [], path };
282
- }, [routes, path, version]);
283
- }
284
- var useParams = () => usePluginRoute().params;
285
- function Breadcrumbs({ separator = "/" }) {
286
- const { breadcrumbs } = usePluginRoute();
287
- if (!breadcrumbs.length) return null;
288
- return /* @__PURE__ */ jsx("nav", { "aria-label": "Breadcrumb", className: "flux-breadcrumbs", children: breadcrumbs.map((b, i) => /* @__PURE__ */ jsxs("span", { children: [
289
- i > 0 && /* @__PURE__ */ jsxs("span", { className: "flux-breadcrumb-sep", children: [
290
- " ",
291
- separator,
292
- " "
293
- ] }),
294
- i === breadcrumbs.length - 1 ? /* @__PURE__ */ jsx("span", { "aria-current": "page", children: b.label }) : /* @__PURE__ */ jsx(Link, { to: b.path, children: b.label })
295
- ] }, b.path)) });
296
- }
297
- var lazyCache = /* @__PURE__ */ new WeakMap();
298
- function resolveComponent(def) {
299
- if (def.component) return def.component;
300
- const loader = def.lazy;
301
- let c = lazyCache.get(loader);
302
- if (!c) {
303
- c = lazy(async () => {
304
- const mod = await loader();
305
- const comp = mod && typeof mod === "object" && "default" in mod ? mod.default : mod;
306
- return { default: comp };
307
- });
308
- lazyCache.set(loader, c);
309
- }
310
- return c;
311
- }
312
- function PluginRoutes({ notFound, forbidden, loading, defaultLayout: DefaultLayout }) {
313
- const fw = usePluginFramework();
314
- const path = useLocation();
315
- const routes = fw.manager.routes;
316
- useSyncExternalStore(routes.subscribe, () => routes.version, () => routes.version);
317
- const [resolution, setResolution] = useState(null);
318
- const version = routes.version;
319
- useEffect(() => {
320
- let live = true;
321
- (async () => {
322
- const match2 = await routes.matchAsync(path);
323
- if (!match2) return live && setResolution({ path });
324
- const guard2 = await routes.checkGuards(match2, path);
325
- if (live) setResolution({ path, match: match2, guard: guard2 });
326
- })().catch(() => live && setResolution({ path }));
327
- return () => {
328
- live = false;
329
- };
330
- }, [routes, path, version]);
331
- useEffect(() => {
332
- if (resolution?.path === path && resolution.guard && !resolution.guard.allow && resolution.guard.redirect) navigate(resolution.guard.redirect, { replace: true });
333
- }, [resolution, path]);
334
- if (!resolution || resolution.path !== path) return /* @__PURE__ */ jsx(Fragment, { children: loading ?? null });
335
- const { match, guard } = resolution;
336
- if (!match) return /* @__PURE__ */ jsx(Fragment, { children: notFound ?? /* @__PURE__ */ jsxs("div", { className: "flux-not-found", children: [
337
- "Page not found: ",
338
- path
339
- ] }) });
340
- if (guard && !guard.allow) {
341
- if (guard.redirect) return /* @__PURE__ */ jsx(Fragment, { children: loading ?? null });
342
- return /* @__PURE__ */ jsx(Fragment, { children: forbidden ? forbidden(guard.reason) : /* @__PURE__ */ jsxs("div", { className: "flux-forbidden", children: [
343
- "Access denied: ",
344
- guard.reason
345
- ] }) });
346
- }
347
- const owner = match.route.owner ?? "host";
348
- const Page = resolveComponent(match.route.definition);
349
- let node = /* @__PURE__ */ jsx(Page, { params: match.params });
350
- for (const r of [...match.chain].reverse()) {
351
- const l = r.definition.layout;
352
- if (!l) continue;
353
- const Layout = typeof l === "string" ? routes.getLayout(l) : l;
354
- if (Layout) node = /* @__PURE__ */ jsx(Layout, { children: node });
355
- }
356
- if (!match.chain.some((r) => r.definition.layout) && DefaultLayout) node = /* @__PURE__ */ jsx(DefaultLayout, { children: node });
357
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(PluginBoundary, { owner, children: /* @__PURE__ */ jsx(Suspense, { fallback: loading ?? /* @__PURE__ */ jsx("div", { className: "flux-loading", children: "Loading\u2026" }), children: node }) }, match.route.fullPath) });
358
- }
359
- function useMenu(location) {
360
- const fw = usePluginFramework();
361
- const menus = fw.manager.menus;
362
- useSyncExternalStore(menus.subscribe, () => menus.version, () => menus.version);
363
- const run = useCallback(
364
- (item) => {
365
- if (item.command) return fw.manager.commands.execute(item.command, ...item.args ?? []);
366
- if (item.path) navigate(item.path);
367
- },
368
- [fw]
369
- );
370
- return { items: menus.items(location), run };
371
- }
372
-
373
- // src/react/components.tsx
374
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
375
- var FrameworkContext = createContext(null);
376
- function PluginProvider({ framework, children }) {
377
- return /* @__PURE__ */ jsx2(FrameworkContext.Provider, { value: framework, children });
378
- }
379
- function usePluginFramework() {
380
- const fw = useContext(FrameworkContext);
381
- if (!fw) throw new Error("usePlugin* hooks must be used inside <PluginProvider>");
382
- return fw;
383
- }
384
- var PluginErrorBoundary = class extends Component {
385
- state = { error: null };
386
- static getDerivedStateFromError(error) {
387
- return { error };
388
- }
389
- componentDidCatch(error, _info) {
390
- this.props.framework.reportRenderError(this.props.owner, error);
391
- }
392
- reset = () => this.setState({ error: null });
393
- render() {
394
- const { error } = this.state;
395
- if (!error) return this.props.children;
396
- if (this.props.fallback) return this.props.fallback(error, this.reset);
397
- return /* @__PURE__ */ jsxs2("div", { role: "alert", className: "fluxplugin-error", "data-flux-error": this.props.owner, children: [
398
- /* @__PURE__ */ jsx2("strong", { children: this.props.owner }),
399
- " failed to render.",
400
- " ",
401
- /* @__PURE__ */ jsx2("button", { type: "button", onClick: this.reset, children: "Retry" })
402
- ] });
403
- }
404
- };
405
- function PluginBoundary({ owner, children }) {
406
- const fw = usePluginFramework();
407
- return /* @__PURE__ */ jsx2("div", { "data-fluxplugin": owner, style: { display: "contents" }, children: /* @__PURE__ */ jsx2(PluginErrorBoundary, { owner, framework: fw, children }) });
408
- }
409
- function useRegistryVersion(subscribe, version) {
410
- return useSyncExternalStore2(subscribe, version, version);
411
- }
412
- function renderContribution(c, props) {
413
- const C = c.component;
414
- return /* @__PURE__ */ jsx2(PluginBoundary, { owner: c.owner ?? "unknown", children: /* @__PURE__ */ jsx2(C, { ...c.props ?? {}, ...props }) }, c.id);
415
- }
416
- function PluginSlot({ name, children, props = {} }) {
417
- const fw = usePluginFramework();
418
- useRegistryVersion(fw.manager.slots.subscribe, () => fw.manager.slots.version);
419
- const resolved = fw.manager.slots.resolve(name, props);
420
- return /* @__PURE__ */ jsxs2(Fragment2, { children: [
421
- resolved.before.map((c) => renderContribution(c, props)),
422
- resolved.replacement ? renderContribution(resolved.replacement, { ...props, children }) : children,
423
- resolved.after.map((c) => renderContribution(c, props))
424
- ] });
425
- }
426
- function useExtensionItems(point, key) {
427
- const fw = usePluginFramework();
428
- const version = useRegistryVersion(fw.manager.extensionPoints.subscribe, () => fw.manager.extensionPoints.version);
429
- return useMemo2(() => fw.manager.extensionPoints.items(point, key), [fw, point, key, version]);
430
- }
431
- function usePlugin(id) {
432
- const fw = usePluginFramework();
433
- useRegistryVersion(fw.subscribe, () => fw.storeVersion);
434
- return fw.manager.has(id) ? fw.manager.info(id) : void 0;
435
- }
436
- function usePlugins() {
437
- const fw = usePluginFramework();
438
- useRegistryVersion(fw.subscribe, () => fw.storeVersion);
439
- return fw.manager.list();
440
- }
441
- function usePluginState(pluginId, key, initial) {
442
- const fw = usePluginFramework();
443
- const store = fw.manager.stateOf(pluginId);
444
- const sub = useCallback2((l) => store.subscribe(l, key).dispose, [store, key]);
445
- const value = useSyncExternalStore2(
446
- sub,
447
- () => store.get(key, initial),
448
- () => initial
449
- );
450
- const set = useCallback2((v) => store.set(key, typeof v === "function" ? v(store.get(key, initial)) : v), [store, key, initial]);
451
- return [value, set];
452
- }
453
- function usePluginEvent(name, onEvent) {
454
- const fw = usePluginFramework();
455
- const [last, setLast] = useState2(void 0);
456
- const cb = useRef(onEvent);
457
- cb.current = onEvent;
458
- useEffect2(() => {
459
- const handle = (p) => {
460
- setLast(p);
461
- cb.current?.(p);
462
- };
463
- const subs = [fw.manager.events.on(name, (p) => handle(p)), fw.bridge.on(name, handle), fw.bridge.on(`flux.event.${name}`, handle)];
464
- return () => {
465
- subs.forEach((s2) => typeof s2 === "function" ? s2() : s2.dispose());
466
- };
467
- }, [fw, name]);
468
- return last;
469
- }
470
- function usePluginCommand(id) {
471
- const fw = usePluginFramework();
472
- useRegistryVersion(fw.manager.commands.subscribe, () => fw.manager.commands.version);
473
- const cmd = fw.manager.commands.get(id);
474
- const run = useCallback2((...args) => fw.manager.commands.execute(id, ...args), [fw, id]);
475
- return { run, available: !!cmd && (!cmd.when || cmd.when()), title: cmd?.title };
476
- }
477
- function usePluginService(name) {
478
- const fw = usePluginFramework();
479
- useRegistryVersion(fw.manager.services.subscribe, () => fw.manager.services.version);
480
- useEffect2(() => {
481
- if (!fw.manager.services.has(name)) void fw.manager.services.resolveAsync(name).catch(() => void 0);
482
- }, [fw, name]);
483
- return fw.manager.services.resolve(name);
484
- }
485
- function usePluginIPC(endpoint, opts = {}) {
486
- const fw = usePluginFramework();
487
- const name = typeof endpoint === "string" ? endpoint : endpoint.name;
488
- const [state, setState] = useState2({ data: void 0, error: void 0, loading: false });
489
- const alive = useRef(true);
490
- useEffect2(() => () => void (alive.current = false), []);
491
- const call = useCallback2(
492
- async (input) => {
493
- setState((s2) => ({ ...s2, loading: true, error: void 0 }));
494
- try {
495
- const data = await fw.bridge.invoke(name, input, opts.caller);
496
- if (alive.current) setState({ data, error: void 0, loading: false });
497
- return data;
498
- } catch (e) {
499
- const error = e instanceof Error ? e : new Error(String(e));
500
- if (alive.current) setState({ data: void 0, error, loading: false });
501
- throw error;
502
- }
503
- },
504
- [fw, name, opts.caller]
505
- );
506
- return { ...state, call };
507
- }
508
- function usePluginConfig(pluginId) {
509
- const fw = usePluginFramework();
510
- const handle = useMemo2(() => fw.configOf(pluginId), [fw, pluginId]);
511
- const [config, setConfig] = useState2();
512
- const [error, setError] = useState2();
513
- const [loading, setLoading] = useState2(true);
514
- useEffect2(() => {
515
- let live = true;
516
- setLoading(true);
517
- handle.get().then((c) => live && (setConfig(c), setError(void 0))).catch((e) => live && setError(e)).finally(() => live && setLoading(false));
518
- const sub = fw.manager.events.on("plugin.config.changed", (p) => {
519
- if (p?.id === pluginId) setConfig(p.next);
520
- });
521
- return () => {
522
- live = false;
523
- sub.dispose();
524
- };
525
- }, [fw, handle, pluginId]);
526
- return {
527
- config,
528
- loading,
529
- error,
530
- set: async (patch) => {
531
- const next = await handle.set(patch);
532
- setConfig(next);
533
- return next;
534
- },
535
- reset: async () => {
536
- const next = await handle.reset();
537
- setConfig(next);
538
- return next;
539
- }
540
- };
541
- }
542
- function StyleHost() {
543
- const fw = usePluginFramework();
544
- useRegistryVersion(fw.manager.styles.subscribe, () => fw.manager.styles.version);
545
- const entries = fw.manager.styles.ordered();
546
- const sig = entries.map((e) => `${e.owner}/${e.id}/${e.priority}/${e.css?.length ?? 0}/${e.href ?? ""}`).join("|");
547
- useEffect2(() => {
548
- const created = [];
549
- for (const e of entries) {
550
- let el;
551
- if (e.href) {
552
- const link = document.createElement("link");
553
- link.rel = "stylesheet";
554
- link.href = e.href;
555
- el = link;
556
- } else {
557
- const style = document.createElement("style");
558
- style.textContent = e.scoped ? scopeCss(e.css ?? "", e.owner) : e.css ?? "";
559
- el = style;
560
- }
561
- el.setAttribute("data-flux-style", `${e.owner}:${e.id}`);
562
- document.head.appendChild(el);
563
- created.push(el);
564
- }
565
- return () => created.forEach((el) => el.remove());
566
- }, [sig]);
567
- return null;
568
- }
569
-
570
- // src/react/palette.tsx
571
- import { useEffect as useEffect3, useMemo as useMemo3, useRef as useRef2, useState as useState3, useSyncExternalStore as useSyncExternalStore3 } from "react";
572
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
573
- function useKeybindings(opts = {}) {
574
- const fw = usePluginFramework();
575
- useEffect3(() => {
576
- if (opts.enabled === false) return;
577
- const onKey = (e) => {
578
- if (e.defaultPrevented) return;
579
- const target = e.target;
580
- if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) && !(e.ctrlKey || e.metaKey)) return;
581
- const parts = [];
582
- if (e.ctrlKey || e.metaKey) parts.push("mod");
583
- if (e.shiftKey) parts.push("shift");
584
- if (e.altKey) parts.push("alt");
585
- const key = e.key.toLowerCase();
586
- if (["control", "shift", "alt", "meta"].includes(key)) return;
587
- parts.push(key);
588
- const cmd = fw.manager.commands.byKeybinding(parts.join("+"));
589
- if (cmd) {
590
- e.preventDefault();
591
- void fw.manager.commands.execute(cmd.id).catch((err) => fw.manager.logs.logger("host").error(`command ${cmd.id} failed: ${String(err)}`));
592
- }
593
- };
594
- window.addEventListener("keydown", onKey);
595
- return () => window.removeEventListener("keydown", onKey);
596
- }, [fw, opts.enabled]);
597
- }
598
- function CommandPalette({ open, onClose }) {
599
- const fw = usePluginFramework();
600
- const cmds = fw.manager.commands;
601
- useSyncExternalStore3(cmds.subscribe, () => cmds.version, () => cmds.version);
602
- const [internal, setInternal] = useState3(false);
603
- const isOpen = open ?? internal;
604
- const close = () => onClose ? onClose() : setInternal(false);
605
- const [query, setQuery] = useState3("");
606
- const [index, setIndex] = useState3(0);
607
- const input = useRef2(null);
608
- useEffect3(() => {
609
- const onKey = (e) => {
610
- if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") {
611
- e.preventDefault();
612
- setInternal((v) => !v);
613
- } else if (e.key === "Escape") close();
614
- };
615
- window.addEventListener("keydown", onKey);
616
- return () => window.removeEventListener("keydown", onKey);
617
- }, []);
618
- useEffect3(() => {
619
- if (isOpen) {
620
- setQuery("");
621
- setIndex(0);
622
- setTimeout(() => input.current?.focus(), 0);
623
- }
624
- }, [isOpen]);
625
- const results = useMemo3(() => cmds.palette(query), [cmds, query, cmds.version]);
626
- if (!isOpen) return null;
627
- const run = (i) => {
628
- const c = results[i];
629
- if (!c) return;
630
- close();
631
- void cmds.execute(c.id).catch((e) => fw.manager.logs.logger("host").error(String(e)));
632
- };
633
- return /* @__PURE__ */ jsx3("div", { className: "flux-palette-backdrop", onMouseDown: close, role: "presentation", children: /* @__PURE__ */ jsxs3("div", { className: "flux-palette", role: "dialog", "aria-label": "Command palette", onMouseDown: (e) => e.stopPropagation(), children: [
634
- /* @__PURE__ */ jsx3(
635
- "input",
636
- {
637
- ref: input,
638
- className: "flux-palette-input",
639
- placeholder: "Type a command\u2026",
640
- value: query,
641
- onChange: (e) => (setQuery(e.target.value), setIndex(0)),
642
- onKeyDown: (e) => {
643
- if (e.key === "ArrowDown") e.preventDefault(), setIndex((i) => Math.min(i + 1, results.length - 1));
644
- else if (e.key === "ArrowUp") e.preventDefault(), setIndex((i) => Math.max(i - 1, 0));
645
- else if (e.key === "Enter") run(index);
646
- }
647
- }
648
- ),
649
- /* @__PURE__ */ jsxs3("ul", { className: "flux-palette-list", role: "listbox", children: [
650
- results.length === 0 && /* @__PURE__ */ jsx3("li", { className: "flux-palette-empty", children: "No matching commands" }),
651
- results.map((c, i) => /* @__PURE__ */ jsxs3("li", { role: "option", "aria-selected": i === index, className: "flux-palette-item" + (i === index ? " is-active" : ""), onMouseEnter: () => setIndex(i), onClick: () => run(i), children: [
652
- /* @__PURE__ */ jsxs3("span", { children: [
653
- c.category && /* @__PURE__ */ jsxs3("span", { className: "flux-palette-cat", children: [
654
- c.category,
655
- ": "
656
- ] }),
657
- c.title
658
- ] }),
659
- c.keybinding && /* @__PURE__ */ jsx3("kbd", { children: c.keybinding.replace("Mod", /Mac/.test(navigator.platform) ? "\u2318" : "Ctrl") })
660
- ] }, c.id))
661
- ] })
662
- ] }) });
663
- }
664
-
665
- // src/react/base-css.ts
666
- var baseCss = `
667
- :root{--flux-bg:#fff;--flux-fg:#0f172a;--flux-muted:#64748b;--flux-border:#e2e8f0;--flux-accent:#4f46e5;--flux-accent-fg:#fff;--flux-surface:#f8fafc;--flux-danger:#dc2626;--flux-ok:#16a34a;--flux-warn:#d97706;--flux-radius:10px}
668
- @media (prefers-color-scheme: dark){:root:not([data-theme="light"]){--flux-bg:#0b1020;--flux-fg:#e5e7eb;--flux-muted:#94a3b8;--flux-border:#1f2a44;--flux-surface:#111936}}
669
- .fluxplugin-error{padding:8px 12px;border:1px solid var(--flux-danger);border-radius:var(--flux-radius);color:var(--flux-danger);font-size:13px}
670
- .flux-breadcrumbs{font-size:13px;color:var(--flux-muted)}.flux-breadcrumbs a{color:inherit}
671
- .flux-not-found,.flux-forbidden,.flux-loading{padding:24px;color:var(--flux-muted)}
672
- .flux-palette-backdrop{position:fixed;inset:0;background:rgba(2,6,23,.45);display:flex;justify-content:center;align-items:flex-start;padding-top:14vh;z-index:9999}
673
- .flux-palette{width:min(560px,92vw);background:var(--flux-bg);color:var(--flux-fg);border:1px solid var(--flux-border);border-radius:14px;box-shadow:0 24px 60px rgba(0,0,0,.35);overflow:hidden}
674
- .flux-palette-input{width:100%;box-sizing:border-box;padding:14px 16px;border:0;border-bottom:1px solid var(--flux-border);background:transparent;color:inherit;font-size:15px;outline:none}
675
- .flux-palette-list{list-style:none;margin:0;padding:6px;max-height:340px;overflow:auto}
676
- .flux-palette-item{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:9px 12px;border-radius:8px;cursor:pointer;font-size:14px}
677
- .flux-palette-item.is-active{background:var(--flux-accent);color:var(--flux-accent-fg)}
678
- .flux-palette-cat{opacity:.65}.flux-palette-empty{padding:14px;color:var(--flux-muted);font-size:14px}
679
- .flux-palette kbd{font:11px ui-monospace,monospace;padding:2px 6px;border:1px solid currentColor;border-radius:5px;opacity:.7}
680
-
681
- :root[data-theme="dark"]{--flux-bg:#0b1020;--flux-fg:#e5e7eb;--flux-muted:#94a3b8;--flux-border:#1f2a44;--flux-surface:#111936}
682
- :root[data-theme="light"]{--flux-bg:#fff;--flux-fg:#0f172a;--flux-muted:#64748b;--flux-border:#e2e8f0;--flux-surface:#f8fafc}
683
- .flux-icon{flex:none;vertical-align:middle}
684
- .flux-titlebar{--flux-titlebar-h:40px;height:var(--flux-titlebar-h);box-sizing:border-box;display:grid;grid-template-columns:1fr minmax(160px,400px) 1fr;align-items:center;gap:8px;padding:0 8px;background:var(--flux-surface);border-bottom:1px solid var(--flux-border);color:var(--flux-fg);-webkit-app-region:drag;user-select:none;position:relative;z-index:50}
685
- .flux-titlebar[data-electron][data-os="mac"]{padding-left:82px}
686
- .flux-titlebar[data-electron][data-os="windows"],.flux-titlebar[data-electron][data-os="linux"]{padding-right:calc(100vw - env(titlebar-area-x,0px) - env(titlebar-area-width,100vw) + 8px)}
687
- .flux-titlebar button,.flux-titlebar input,.flux-titlebar select,.flux-titlebar .flux-dropdown{-webkit-app-region:no-drag}
688
- .flux-tb-left,.flux-tb-right{display:flex;align-items:center;gap:4px;min-width:0}.flux-tb-right{justify-content:flex-end}
689
- .flux-brand{display:flex;align-items:center;gap:8px;font-weight:600;margin-right:8px;white-space:nowrap}
690
- .flux-tb-btn{display:inline-flex;align-items:center;gap:6px;border:0;background:transparent;color:inherit;border-radius:6px;padding:5px 9px;font:inherit;cursor:pointer;position:relative}
691
- .flux-tb-btn:hover,.flux-tb-btn.is-open{background:var(--flux-border)}
692
- .flux-menubar{display:flex}.flux-menu{position:relative}
693
- .flux-dropdown{position:absolute;top:calc(100% + 4px);left:0;min-width:220px;max-width:360px;margin:0;padding:6px;list-style:none;background:var(--flux-bg);color:var(--flux-fg);border:1px solid var(--flux-border);border-radius:10px;box-shadow:0 14px 36px rgba(0,0,0,.28);z-index:60}
694
- .flux-dd-item{display:flex;justify-content:space-between;align-items:center;gap:16px;width:100%;border:0;background:transparent;color:inherit;font:inherit;text-align:left;padding:7px 10px;border-radius:7px;cursor:pointer}
695
- .flux-dd-item:hover,.flux-dd-item.is-active{background:var(--flux-accent);color:var(--flux-accent-fg)}
696
- .flux-dd-item kbd,.flux-dd-item small{opacity:.65;font:11px ui-monospace,monospace}.flux-sep{height:1px;background:var(--flux-border);margin:5px 4px}
697
- .flux-search{position:relative;display:flex;align-items:center;gap:8px;background:var(--flux-bg);border:1px solid var(--flux-border);border-radius:8px;padding:0 10px;height:28px;color:var(--flux-muted)}
698
- .flux-search input{flex:1;min-width:0;border:0;background:transparent;color:var(--flux-fg);font:inherit;outline:none}
699
- .flux-search-results{left:0;right:0;max-width:none;top:calc(100% + 6px)}.flux-group{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--flux-muted);padding:6px 10px 2px}
700
- .flux-empty{padding:12px;color:var(--flux-muted);font-size:13px}.flux-select{margin-left:4px;background:var(--flux-bg);color:inherit;border:1px solid var(--flux-border);border-radius:6px;padding:3px 4px;font:inherit}
701
- .flux-bell{position:relative}.flux-badge{position:absolute;top:0;right:0;min-width:15px;height:15px;border-radius:99px;background:var(--flux-danger);color:#fff;font-size:10px;line-height:15px;text-align:center}
702
- .flux-notif-panel{left:auto;right:0;width:340px;max-height:420px;overflow:auto;padding:0}.flux-notif-head{display:flex;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--flux-border)}
703
- .flux-notif{display:flex;justify-content:space-between;gap:10px;padding:10px 12px;border-bottom:1px solid var(--flux-border);border-left:3px solid var(--flux-accent)}
704
- .flux-notif.flux-success{border-left-color:var(--flux-ok)}.flux-notif.flux-warning{border-left-color:var(--flux-warn)}.flux-notif.flux-error{border-left-color:var(--flux-danger)}
705
- .flux-notif p{margin:2px 0 4px;font-size:13px}.flux-notif small{color:var(--flux-muted)}.flux-notif-actions{display:flex;flex-direction:column;align-items:flex-end;gap:4px}
706
- .flux-link{background:none;border:0;color:var(--flux-accent);cursor:pointer;font:inherit;font-size:12px;padding:0}
707
- .flux-toasts{position:fixed;top:48px;right:12px;display:flex;flex-direction:column;gap:8px;z-index:40;pointer-events:none}
708
- .flux-toast{pointer-events:auto;background:var(--flux-bg);color:var(--flux-fg);border:1px solid var(--flux-border);border-left:4px solid var(--flux-accent);border-radius:10px;padding:10px 14px;box-shadow:0 10px 28px rgba(0,0,0,.25);max-width:340px;font-size:13px}
709
- .flux-toast.flux-success{border-left-color:var(--flux-ok)}.flux-toast.flux-warning{border-left-color:var(--flux-warn)}.flux-toast.flux-error{border-left-color:var(--flux-danger)}
710
- .flux-md{line-height:1.6}.flux-md h2,.flux-md h3,.flux-md h4,.flux-md h5{margin:1.1em 0 .4em}.flux-md pre{background:var(--flux-surface);padding:10px;border-radius:8px;overflow:auto}
711
- .flux-md code{background:var(--flux-surface);padding:1px 5px;border-radius:4px;font-size:.92em}.flux-md pre code{padding:0;background:none}.flux-md table{border-collapse:collapse;margin:8px 0;font-size:13px}.flux-md th,.flux-md td{border:1px solid var(--flux-border);padding:5px 10px;text-align:left}.flux-md th{background:var(--flux-surface)}.flux-md blockquote{margin:8px 0;padding:2px 12px;border-left:3px solid var(--flux-border);color:var(--flux-muted)}
712
- `;
713
-
714
- // src/react/http-bridge.ts
715
- var HttpBridgeError = class extends Error {
716
- constructor(message, code) {
717
- super(message);
718
- this.code = code;
719
- this.name = "HttpBridgeError";
720
- }
721
- code;
722
- };
723
- function createHttpBridge(baseUrl = "") {
724
- const listeners2 = /* @__PURE__ */ new Map();
725
- let source;
726
- const ensureSource = () => {
727
- if (source || typeof EventSource === "undefined") return;
728
- source = new EventSource(`${baseUrl}/flux/events`);
729
- source.onmessage = (ev) => {
730
- try {
731
- const msg = JSON.parse(ev.data);
732
- for (const l of [...listeners2.get(msg.channel) ?? []]) l(msg.payload);
733
- } catch {
734
- }
735
- };
736
- };
737
- const post = (path, body) => fetch(`${baseUrl}${path}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
738
- return {
739
- async invoke(channel, input, caller) {
740
- const res = await (await post("/flux/invoke", { channel, input, caller })).json();
741
- if (!res.ok) throw new HttpBridgeError(res.error.message, res.error.code);
742
- return res.value;
743
- },
744
- send(channel, payload, caller) {
745
- void post("/flux/send", { channel, payload, caller });
746
- },
747
- on(channel, listener) {
748
- ensureSource();
749
- let set = listeners2.get(channel);
750
- if (!set) listeners2.set(channel, set = /* @__PURE__ */ new Set());
751
- set.add(listener);
752
- return () => void set.delete(listener);
753
- },
754
- async bootstrap() {
755
- return await (await fetch(`${baseUrl}/flux/bootstrap`)).json();
756
- }
757
- };
758
- }
759
-
760
- // src/react/os.ts
761
- import { useMemo as useMemo4 } from "react";
762
- function detectOS(ua = typeof navigator === "undefined" ? "" : navigator.userAgent) {
763
- if (/Mac|iPhone|iPad/i.test(ua)) return "mac";
764
- if (/Windows/i.test(ua)) return "windows";
765
- if (/Linux|X11|CrOS/i.test(ua)) return "linux";
766
- return "other";
767
- }
768
- var useOS = () => useMemo4(() => detectOS(), []);
769
- function formatShortcut(binding, os = detectOS()) {
770
- const mac = os === "mac";
771
- const map = mac ? { mod: "\u2318", ctrl: "\u2303", shift: "\u21E7", alt: "\u2325" } : { mod: "Ctrl", ctrl: "Ctrl", shift: "Shift", alt: "Alt" };
772
- const parts = binding.split("+").map((p) => map[p.toLowerCase()] ?? (p.length === 1 ? p.toUpperCase() : p));
773
- return mac ? parts.join("") : parts.join("+");
774
- }
775
-
776
- // src/react/icons.tsx
777
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
778
- var PATHS = {
779
- search: "M11 4a7 7 0 105.2 11.7l3.5 3.5 1.4-1.4-3.5-3.5A7 7 0 0011 4zm0 2a5 5 0 110 10 5 5 0 010-10z",
780
- bell: "M12 3a6 6 0 00-6 6v3.6L4.3 15.3A1 1 0 005 17h14a1 1 0 00.7-1.7L18 12.6V9a6 6 0 00-6-6zm-2 16a2 2 0 004 0h-4z",
781
- sun: "M12 7a5 5 0 100 10 5 5 0 000-10zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM1 11h3v2H1v-2zm19 0h3v2h-3v-2zM4.2 5.6l1.4-1.4 2.1 2.1-1.4 1.4-2.1-2.1zm12.1 12.1l1.4-1.4 2.1 2.1-1.4 1.4-2.1-2.1zM5.6 19.8l-1.4-1.4 2.1-2.1 1.4 1.4-2.1 2.1zM18.4 7.7l-1.4-1.4 2.1-2.1 1.4 1.4-2.1 2.1z",
782
- moon: "M20 14.5A8.5 8.5 0 019.5 4a8.5 8.5 0 1010.5 10.5z",
783
- plug: "M9 2v5H7v3a5 5 0 004 4.9V19H8v2h8v-2h-3v-4.1A5 5 0 0017 10V7h-2V2h-2v5h-2V2H9z",
784
- settings: "M12 8a4 4 0 100 8 4 4 0 000-8zm8.9 5l1.7 1.3-2 3.4-2-.8a7.6 7.6 0 01-1.7 1l-.3 2.1h-4l-.3-2.1a7.6 7.6 0 01-1.7-1l-2 .8-2-3.4L5.3 13a7 7 0 010-2L3.6 9.7l2-3.4 2 .8a7.6 7.6 0 011.7-1L9.6 4h4l.3 2.1a7.6 7.6 0 011.7 1l2-.8 2 3.4L17.9 11a7 7 0 010 2z",
785
- check: "M9 16.2l-3.5-3.5L4 14.2l5 5 11-11-1.5-1.5L9 16.2z",
786
- close: "M6.4 5l-1.4 1.4L10.6 12 5 17.6 6.4 19l5.6-5.6 5.6 5.6 1.4-1.4-5.6-5.6L19 6.4 17.6 5 12 10.6 6.4 5z",
787
- chevron: "M7 10l5 5 5-5H7z",
788
- play: "M8 5v14l11-7L8 5z",
789
- image: "M4 5h16a1 1 0 011 1v12a1 1 0 01-1 1H4a1 1 0 01-1-1V6a1 1 0 011-1zm3 3a2 2 0 100 4 2 2 0 000-4zm-3 9h16v-3l-4-4-5 5-3-3-4 4v1z",
790
- command: "M7 3a4 4 0 00-1 7.9v2.2A4 4 0 107 21a4 4 0 004-4h2a4 4 0 104-4 4 4 0 00-1-.1V10.9A4 4 0 1017 3a4 4 0 00-4 4h-2a4 4 0 00-4-4zm4 9H9V10h2v2zm4 0h-2V10h2v2z"
791
- };
792
- function Icon({ name, d, size = 16, title }) {
793
- const path = d ?? (name ? PATHS[name] : void 0) ?? PATHS["plug"];
794
- return /* @__PURE__ */ jsxs4("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": title ? void 0 : true, role: title ? "img" : void 0, className: "flux-icon", children: [
795
- title && /* @__PURE__ */ jsx4("title", { children: title }),
796
- /* @__PURE__ */ jsx4("path", { d: path })
797
- ] });
798
- }
799
- var iconNames = Object.keys(PATHS);
800
-
801
- // src/react/markdown.tsx
802
- import React4 from "react";
803
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
804
- var safeHref = (h) => /^https:\/\/[^\s]+$/i.test(h) || h.startsWith("#") ? h : void 0;
805
- function inline(src, key = 0) {
806
- const out = [];
807
- const re = /(`[^`]+`)|(\*\*[^*]+\*\*)|(\*[^*\s][^*]*\*)|(\[[^\]]+\]\([^)\s]+\))/g;
808
- let last = 0;
809
- let m;
810
- let i = 0;
811
- while (m = re.exec(src)) {
812
- if (m.index > last) out.push(src.slice(last, m.index));
813
- const tok = m[0];
814
- const k = `${key}-${i++}`;
815
- if (tok.startsWith("`")) out.push(/* @__PURE__ */ jsx5("code", { children: tok.slice(1, -1) }, k));
816
- else if (tok.startsWith("**")) out.push(/* @__PURE__ */ jsx5("strong", { children: tok.slice(2, -2) }, k));
817
- else if (tok.startsWith("*")) out.push(/* @__PURE__ */ jsx5("em", { children: tok.slice(1, -1) }, k));
818
- else {
819
- const mm = /^\[([^\]]+)\]\(([^)\s]+)\)$/.exec(tok);
820
- const href = safeHref(mm[2]);
821
- out.push(href ? /* @__PURE__ */ jsx5("a", { href, target: "_blank", rel: "noopener noreferrer", children: mm[1] }, k) : mm[1]);
822
- }
823
- last = m.index + tok.length;
824
- }
825
- if (last < src.length) out.push(src.slice(last));
826
- return out;
827
- }
828
- function Markdown({ source }) {
829
- const lines = source.replace(/\r\n/g, "\n").split("\n");
830
- const blocks = [];
831
- let i = 0;
832
- let k = 0;
833
- while (i < lines.length) {
834
- const line = lines[i];
835
- if (!line.trim()) {
836
- i++;
837
- continue;
838
- }
839
- if (line.startsWith("```")) {
840
- const buf2 = [];
841
- i++;
842
- while (i < lines.length && !lines[i].startsWith("```")) buf2.push(lines[i++]);
843
- i++;
844
- blocks.push(/* @__PURE__ */ jsx5("pre", { children: /* @__PURE__ */ jsx5("code", { children: buf2.join("\n") }) }, k++));
845
- continue;
846
- }
847
- const h = /^(#{1,4})\s+(.*)$/.exec(line);
848
- if (h) {
849
- const L = `h${h[1].length + 1}`;
850
- blocks.push(React4.createElement(L, { key: k++ }, inline(h[2])));
851
- i++;
852
- continue;
853
- }
854
- if (/^(-{3,}|\*{3,})$/.test(line.trim())) {
855
- blocks.push(/* @__PURE__ */ jsx5("hr", {}, k++));
856
- i++;
857
- continue;
858
- }
859
- if (line.startsWith(">")) {
860
- const buf2 = [];
861
- while (i < lines.length && lines[i].startsWith(">")) buf2.push(lines[i++].replace(/^>\s?/, ""));
862
- blocks.push(/* @__PURE__ */ jsx5("blockquote", { children: inline(buf2.join(" ")) }, k++));
863
- continue;
864
- }
865
- if (line.includes("|") && /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?\s*$/.test(lines[i + 1] ?? "")) {
866
- const cells = (l) => l.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
867
- const head = cells(line);
868
- i += 2;
869
- const rows = [];
870
- while (i < lines.length && lines[i].includes("|") && lines[i].trim()) rows.push(cells(lines[i++]));
871
- blocks.push(
872
- /* @__PURE__ */ jsxs5("table", { children: [
873
- /* @__PURE__ */ jsx5("thead", { children: /* @__PURE__ */ jsx5("tr", { children: head.map((c, j) => /* @__PURE__ */ jsx5("th", { children: inline(c) }, j)) }) }),
874
- /* @__PURE__ */ jsx5("tbody", { children: rows.map((r, ri) => /* @__PURE__ */ jsx5("tr", { children: r.map((c, j) => /* @__PURE__ */ jsx5("td", { children: inline(c) }, j)) }, ri)) })
875
- ] }, k++)
876
- );
877
- continue;
878
- }
879
- const li = /^(\s*)([-*]|\d+\.)\s+/;
880
- if (li.test(line)) {
881
- const ordered = /^\s*\d+\./.test(line);
882
- const items = [];
883
- while (i < lines.length && li.test(lines[i])) items.push(/* @__PURE__ */ jsx5("li", { children: inline(lines[i++].replace(li, "")) }, items.length));
884
- blocks.push(ordered ? /* @__PURE__ */ jsx5("ol", { children: items }, k++) : /* @__PURE__ */ jsx5("ul", { children: items }, k++));
885
- continue;
886
- }
887
- const buf = [];
888
- while (i < lines.length && lines[i].trim() && !/^(#{1,4}\s|```|>|\s*([-*]|\d+\.)\s)/.test(lines[i]) && !(lines[i].includes("|") && /^\s*\|?\s*:?-{2,}/.test(lines[i + 1] ?? ""))) buf.push(lines[i++]);
889
- blocks.push(/* @__PURE__ */ jsx5("p", { children: inline(buf.join(" ")) }, k++));
890
- }
891
- return /* @__PURE__ */ jsx5("div", { className: "flux-md", children: blocks });
892
- }
893
-
894
- // src/react/theme.tsx
895
- import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useEffect as useEffect4, useMemo as useMemo5, useState as useState4 } from "react";
896
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
897
- function declareHostPoints(framework) {
898
- const ep = framework.manager.extensionPoints;
899
- if (!ep.definition("theme")) {
900
- ep.declare({ name: "theme", contributions: { themes: s.object({ id: s.string(), label: s.string(), dark: s.boolean().optional(), tokens: s.record(s.string()) }) } });
901
- }
902
- if (!ep.definition("titlebar")) {
903
- ep.declare({ name: "titlebar", contributions: { searchProviders: s.object({ id: s.string(), label: s.string(), search: s.fn() }) } });
904
- }
905
- }
906
- var Ctx = createContext2(null);
907
- var read = (k) => {
908
- try {
909
- return localStorage.getItem(k);
910
- } catch {
911
- return null;
912
- }
913
- };
914
- var write = (k, v) => {
915
- try {
916
- v === void 0 ? localStorage.removeItem(k) : localStorage.setItem(k, v);
917
- } catch {
918
- }
919
- };
920
- function ThemeProvider({ children, storageKey = "fluxplugin.theme", onResolved }) {
921
- const themes = useExtensionItems("theme", "themes");
922
- const [mode, setModeState] = useState4(() => read(storageKey + ".mode") || "system");
923
- const [themeId, setThemeIdState] = useState4(() => read(storageKey + ".id") ?? void 0);
924
- const [systemDark, setSystemDark] = useState4(() => typeof matchMedia !== "undefined" && matchMedia("(prefers-color-scheme: dark)").matches);
925
- useEffect4(() => {
926
- if (typeof matchMedia === "undefined") return;
927
- const mq = matchMedia("(prefers-color-scheme: dark)");
928
- const l = (e) => setSystemDark(e.matches);
929
- mq.addEventListener("change", l);
930
- return () => mq.removeEventListener("change", l);
931
- }, []);
932
- const theme = themes.find((t) => t.id === themeId);
933
- const resolved = theme ? theme.dark ? "dark" : "light" : mode === "system" ? systemDark ? "dark" : "light" : mode;
934
- useEffect4(() => {
935
- const root = document.documentElement;
936
- root.setAttribute("data-theme", resolved);
937
- const applied = Object.entries(theme?.tokens ?? {}).filter(([k]) => /^--[\w-]+$/.test(k));
938
- for (const [k, v] of applied) root.style.setProperty(k, v);
939
- const cs = getComputedStyle(root);
940
- onResolved?.({ dark: resolved === "dark", bg: cs.getPropertyValue("--flux-bg").trim(), fg: cs.getPropertyValue("--flux-fg").trim(), surface: cs.getPropertyValue("--flux-surface").trim() });
941
- return () => applied.forEach(([k]) => root.style.removeProperty(k));
942
- }, [resolved, theme]);
943
- const setMode = useCallback3((m) => (setModeState(m), setThemeIdState(void 0), write(storageKey + ".mode", m), write(storageKey + ".id", void 0)), [storageKey]);
944
- const setThemeId = useCallback3((id) => (setThemeIdState(id), write(storageKey + ".id", id)), [storageKey]);
945
- const value = useMemo5(() => ({ mode, setMode, themeId, setThemeId, resolved, themes, toggle: () => setMode(resolved === "dark" ? "light" : "dark") }), [mode, setMode, themeId, setThemeId, resolved, themes]);
946
- return /* @__PURE__ */ jsx6(Ctx.Provider, { value, children });
947
- }
948
- function useTheme() {
949
- const c = useContext2(Ctx);
950
- if (!c) throw new Error("useTheme must be used inside <ThemeProvider>");
951
- return c;
952
- }
953
- function ThemeSwitcher() {
954
- const { resolved, toggle, themes, themeId, setThemeId, mode, setMode } = useTheme();
955
- usePluginFramework();
956
- return /* @__PURE__ */ jsxs6("span", { className: "flux-theme-switch", children: [
957
- /* @__PURE__ */ jsx6("button", { type: "button", className: "flux-tb-btn", onClick: toggle, title: `Theme: ${resolved} (click to switch)`, "aria-label": "Toggle theme", "data-testid": "theme-toggle", children: /* @__PURE__ */ jsx6(Icon, { name: resolved === "dark" ? "moon" : "sun" }) }),
958
- themes.length > 0 && /* @__PURE__ */ jsxs6("select", { className: "flux-select", "aria-label": "Theme", value: themeId ?? `mode:${mode}`, onChange: (e) => e.target.value.startsWith("mode:") ? setMode(e.target.value.slice(5)) : setThemeId(e.target.value), children: [
959
- /* @__PURE__ */ jsx6("option", { value: "mode:system", children: "System" }),
960
- /* @__PURE__ */ jsx6("option", { value: "mode:light", children: "Light" }),
961
- /* @__PURE__ */ jsx6("option", { value: "mode:dark", children: "Dark" }),
962
- themes.map((t) => /* @__PURE__ */ jsx6("option", { value: t.id, children: t.label }, t.id))
963
- ] })
964
- ] });
965
- }
966
-
967
- // src/react/titlebar.tsx
968
- import { useEffect as useEffect5, useMemo as useMemo6, useRef as useRef3, useState as useState5, useSyncExternalStore as useSyncExternalStore4 } from "react";
969
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
970
- function AppMenuBar() {
971
- const fw = usePluginFramework();
972
- const os = useOS();
973
- const menus = fw.manager.menus;
974
- useSyncExternalStore4(menus.subscribe, () => menus.version, () => menus.version);
975
- const [open, setOpen] = useState5(null);
976
- const ref = useRef3(null);
977
- useEffect5(() => {
978
- const off = (e) => ref.current && !ref.current.contains(e.target) && setOpen(null);
979
- const key = (e) => e.key === "Escape" && setOpen(null);
980
- window.addEventListener("mousedown", off);
981
- window.addEventListener("keydown", key);
982
- return () => (window.removeEventListener("mousedown", off), window.removeEventListener("keydown", key));
983
- }, []);
984
- const top = menus.items("application");
985
- if (!top.length) return null;
986
- const run = (item) => {
987
- setOpen(null);
988
- if (item.command) void fw.manager.commands.execute(item.command, ...item.args ?? []).catch((e) => fw.manager.logs.logger("host").error(String(e)));
989
- else if (item.path) navigate(item.path);
990
- };
991
- const shortcut = (item) => {
992
- const k = item.command ? fw.manager.commands.get(item.command)?.keybinding : void 0;
993
- return k ? formatShortcut(k, os) : "";
994
- };
995
- return /* @__PURE__ */ jsx7("div", { className: "flux-menubar", role: "menubar", ref, children: top.map((m) => /* @__PURE__ */ jsxs7("div", { className: "flux-menu", children: [
996
- /* @__PURE__ */ jsx7(
997
- "button",
998
- {
999
- type: "button",
1000
- role: "menuitem",
1001
- className: "flux-tb-btn" + (open === m.id ? " is-open" : ""),
1002
- "aria-haspopup": !!m.children?.length,
1003
- "aria-expanded": open === m.id,
1004
- onClick: () => m.children?.length ? setOpen(open === m.id ? null : m.id) : run(m),
1005
- onMouseEnter: () => open && m.children?.length && setOpen(m.id),
1006
- children: m.label
1007
- }
1008
- ),
1009
- open === m.id && m.children && /* @__PURE__ */ jsx7("ul", { className: "flux-dropdown", role: "menu", children: m.children.map(
1010
- (c) => c.separator ? /* @__PURE__ */ jsx7("li", { className: "flux-sep", role: "separator" }, c.id) : /* @__PURE__ */ jsx7("li", { role: "none", children: /* @__PURE__ */ jsxs7("button", { type: "button", role: "menuitem", className: "flux-dd-item", onClick: () => run(c), children: [
1011
- /* @__PURE__ */ jsx7("span", { children: c.label }),
1012
- /* @__PURE__ */ jsx7("kbd", { children: shortcut(c) })
1013
- ] }) }, c.id)
1014
- ) })
1015
- ] }, m.id)) });
1016
- }
1017
- function SearchBox({ placeholder = "Search\u2026" }) {
1018
- const fw = usePluginFramework();
1019
- const providers = useExtensionItems("titlebar", "searchProviders");
1020
- const [q, setQ] = useState5("");
1021
- const [results, setResults] = useState5([]);
1022
- const [idx, setIdx] = useState5(0);
1023
- const [focus, setFocus] = useState5(false);
1024
- const input = useRef3(null);
1025
- useEffect5(() => {
1026
- const k = (e) => (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "f" && !e.shiftKey && (e.preventDefault(), input.current?.focus());
1027
- window.addEventListener("keydown", k);
1028
- return () => window.removeEventListener("keydown", k);
1029
- }, []);
1030
- useEffect5(() => {
1031
- const query = q.trim();
1032
- if (!query) return setResults((prev) => prev.length ? [] : prev);
1033
- let live = true;
1034
- (async () => {
1035
- const lower = query.toLowerCase();
1036
- const own = [
1037
- ...fw.manager.commands.palette(query).slice(0, 5).map((c) => ({ id: "cmd:" + c.id, title: c.title, subtitle: c.category, group: "Commands", run: () => void fw.manager.commands.execute(c.id) })),
1038
- ...fw.manager.menus.items("sidebar").filter((m) => m.path && m.label.toLowerCase().includes(lower)).map((m) => ({ id: "page:" + m.id, title: m.label, group: "Pages", run: () => navigate(m.path) }))
1039
- ];
1040
- const extra = await Promise.all(providers.map(async (p) => {
1041
- try {
1042
- return (await p.search(query) ?? []).map((r) => ({ ...r, group: r.group ?? p.label }));
1043
- } catch {
1044
- return [];
1045
- }
1046
- }));
1047
- if (live) setResults([...own, ...extra.flat()].slice(0, 12)), setIdx(0);
1048
- })();
1049
- return () => {
1050
- live = false;
1051
- };
1052
- }, [q, fw, providers]);
1053
- const go = (r) => {
1054
- if (!r) return;
1055
- setQ("");
1056
- input.current?.blur();
1057
- void r.run();
1058
- };
1059
- return /* @__PURE__ */ jsxs7("div", { className: "flux-search", "data-testid": "searchbox", children: [
1060
- /* @__PURE__ */ jsx7(Icon, { name: "search" }),
1061
- /* @__PURE__ */ jsx7(
1062
- "input",
1063
- {
1064
- ref: input,
1065
- value: q,
1066
- placeholder,
1067
- "aria-label": "Search",
1068
- onChange: (e) => setQ(e.target.value),
1069
- onFocus: () => setFocus(true),
1070
- onBlur: () => setTimeout(() => setFocus(false), 120),
1071
- onKeyDown: (e) => {
1072
- if (e.key === "ArrowDown") e.preventDefault(), setIdx((i) => Math.min(i + 1, results.length - 1));
1073
- else if (e.key === "ArrowUp") e.preventDefault(), setIdx((i) => Math.max(i - 1, 0));
1074
- else if (e.key === "Enter") go(results[idx]);
1075
- else if (e.key === "Escape") setQ(""), input.current?.blur();
1076
- }
1077
- }
1078
- ),
1079
- focus && q.trim() && /* @__PURE__ */ jsxs7("ul", { className: "flux-dropdown flux-search-results", role: "listbox", children: [
1080
- results.length === 0 && /* @__PURE__ */ jsx7("li", { className: "flux-empty", children: "No results" }),
1081
- results.map((r, i) => /* @__PURE__ */ jsxs7("li", { role: "option", "aria-selected": i === idx, children: [
1082
- (i === 0 || results[i - 1].group !== r.group) && /* @__PURE__ */ jsx7("div", { className: "flux-group", children: r.group }),
1083
- /* @__PURE__ */ jsxs7("button", { type: "button", className: "flux-dd-item" + (i === idx ? " is-active" : ""), onMouseDown: (e) => (e.preventDefault(), go(r)), children: [
1084
- /* @__PURE__ */ jsx7("span", { children: r.title }),
1085
- r.subtitle && /* @__PURE__ */ jsx7("small", { children: r.subtitle })
1086
- ] })
1087
- ] }, r.id))
1088
- ] })
1089
- ] });
1090
- }
1091
- function useNotifications() {
1092
- const { manager } = usePluginFramework();
1093
- const n = manager.notifications;
1094
- useSyncExternalStore4(n.subscribe, () => n.version, () => n.version);
1095
- return n;
1096
- }
1097
- function NotificationBell() {
1098
- const fw = usePluginFramework();
1099
- const center = useNotifications();
1100
- const [open, setOpen] = useState5(false);
1101
- const ref = useRef3(null);
1102
- useEffect5(() => {
1103
- const off = (e) => ref.current && !ref.current.contains(e.target) && setOpen(false);
1104
- window.addEventListener("mousedown", off);
1105
- return () => window.removeEventListener("mousedown", off);
1106
- }, []);
1107
- const unread = center.unread;
1108
- return /* @__PURE__ */ jsxs7("div", { className: "flux-bell", ref, children: [
1109
- /* @__PURE__ */ jsxs7("button", { type: "button", className: "flux-tb-btn", "aria-label": "Notifications", "data-testid": "bell", onClick: () => (setOpen(!open), !open && center.markRead()), children: [
1110
- /* @__PURE__ */ jsx7(Icon, { name: "bell" }),
1111
- unread > 0 && /* @__PURE__ */ jsx7("span", { className: "flux-badge", "data-testid": "bell-count", children: unread })
1112
- ] }),
1113
- open && /* @__PURE__ */ jsxs7("div", { className: "flux-dropdown flux-notif-panel", role: "dialog", "aria-label": "Notifications", children: [
1114
- /* @__PURE__ */ jsxs7("div", { className: "flux-notif-head", children: [
1115
- /* @__PURE__ */ jsx7("strong", { children: "Notifications" }),
1116
- /* @__PURE__ */ jsx7("button", { type: "button", className: "flux-link", onClick: () => center.clear(), children: "Clear all" })
1117
- ] }),
1118
- center.list().length === 0 && /* @__PURE__ */ jsx7("div", { className: "flux-empty", children: "You're all caught up" }),
1119
- center.list().map((n) => /* @__PURE__ */ jsxs7("div", { className: "flux-notif flux-" + n.level, children: [
1120
- /* @__PURE__ */ jsxs7("div", { className: "flux-notif-main", children: [
1121
- /* @__PURE__ */ jsx7("strong", { children: n.title }),
1122
- n.body && /* @__PURE__ */ jsx7("p", { children: n.body }),
1123
- /* @__PURE__ */ jsxs7("small", { children: [
1124
- n.owner,
1125
- " \xB7 ",
1126
- new Date(n.time).toLocaleTimeString()
1127
- ] })
1128
- ] }),
1129
- /* @__PURE__ */ jsxs7("div", { className: "flux-notif-actions", children: [
1130
- n.actions.map((a) => /* @__PURE__ */ jsx7("button", { type: "button", className: "flux-link", onClick: () => void fw.manager.commands.execute(a.command, ...a.args ?? []), children: a.label }, a.label)),
1131
- /* @__PURE__ */ jsx7("button", { type: "button", className: "flux-link", "aria-label": "Dismiss", onClick: () => center.dismiss(n.id), children: /* @__PURE__ */ jsx7(Icon, { name: "close", size: 12 }) })
1132
- ] })
1133
- ] }, n.id))
1134
- ] })
1135
- ] });
1136
- }
1137
- function ToastHost() {
1138
- const { manager } = usePluginFramework();
1139
- const [toasts, setToasts] = useState5([]);
1140
- useEffect5(() => {
1141
- const timers = [];
1142
- const show = (n, remaining = n.timeoutMs) => {
1143
- if (n.timeoutMs <= 0 || remaining <= 0) return;
1144
- setToasts((t) => [n, ...t.filter((x) => x.id !== n.id)].slice(0, 4));
1145
- timers.push(setTimeout(() => setToasts((t) => t.filter((x) => x.id !== n.id)), remaining));
1146
- };
1147
- for (const n of [...manager.notifications.list()].reverse()) if (!n.read) show(n, n.timeoutMs - (Date.now() - n.time));
1148
- const sub = manager.notifications.onPush((n) => show(n));
1149
- return () => (sub.dispose(), timers.forEach(clearTimeout));
1150
- }, [manager]);
1151
- return /* @__PURE__ */ jsx7("div", { className: "flux-toasts", "aria-live": "polite", "data-testid": "toasts", children: toasts.map((n) => /* @__PURE__ */ jsxs7("div", { className: "flux-toast flux-" + n.level, role: "status", children: [
1152
- /* @__PURE__ */ jsx7("strong", { children: n.title }),
1153
- n.body && /* @__PURE__ */ jsxs7("span", { children: [
1154
- " ",
1155
- n.body
1156
- ] })
1157
- ] }, n.id)) });
1158
- }
1159
- function TitleBar({ title, logo, searchPlaceholder, show }) {
1160
- const os = useOS();
1161
- const electron = typeof window !== "undefined" && !!window.__fluxplugin;
1162
- const s2 = { menu: true, search: true, notifications: true, theme: true, ...show };
1163
- const brand = useMemo6(() => /* @__PURE__ */ jsxs7("div", { className: "flux-brand", children: [
1164
- logo,
1165
- title && /* @__PURE__ */ jsx7("span", { children: title })
1166
- ] }), [logo, title]);
1167
- return /* @__PURE__ */ jsxs7("header", { className: "flux-titlebar", "data-os": os, "data-electron": electron || void 0, "data-testid": "titlebar", children: [
1168
- /* @__PURE__ */ jsxs7("div", { className: "flux-tb-left", children: [
1169
- brand,
1170
- s2.menu && /* @__PURE__ */ jsx7(AppMenuBar, {}),
1171
- /* @__PURE__ */ jsx7(PluginSlot, { name: "titlebar.left" })
1172
- ] }),
1173
- /* @__PURE__ */ jsx7("div", { className: "flux-tb-center", children: s2.search && /* @__PURE__ */ jsx7(SearchBox, { placeholder: searchPlaceholder }) }),
1174
- /* @__PURE__ */ jsxs7("div", { className: "flux-tb-right", children: [
1175
- /* @__PURE__ */ jsx7(PluginSlot, { name: "titlebar.right" }),
1176
- s2.theme && /* @__PURE__ */ jsx7(ThemeSwitcher, {}),
1177
- s2.notifications && /* @__PURE__ */ jsx7(NotificationBell, {})
1178
- ] })
1179
- ] });
1180
- }
1181
-
1182
- export {
1183
- RemoteError,
1184
- PluginsClient,
1185
- RendererFramework,
1186
- buildPluginApis,
1187
- installSharedModules,
1188
- navigate,
1189
- useLocation,
1190
- useNavigate,
1191
- Link,
1192
- usePluginRoute,
1193
- useParams,
1194
- Breadcrumbs,
1195
- PluginRoutes,
1196
- useMenu,
1197
- PluginProvider,
1198
- usePluginFramework,
1199
- PluginErrorBoundary,
1200
- PluginBoundary,
1201
- PluginSlot,
1202
- useExtensionItems,
1203
- usePlugin,
1204
- usePlugins,
1205
- usePluginState,
1206
- usePluginEvent,
1207
- usePluginCommand,
1208
- usePluginService,
1209
- usePluginIPC,
1210
- usePluginConfig,
1211
- StyleHost,
1212
- useKeybindings,
1213
- CommandPalette,
1214
- baseCss,
1215
- HttpBridgeError,
1216
- createHttpBridge,
1217
- detectOS,
1218
- useOS,
1219
- formatShortcut,
1220
- Icon,
1221
- iconNames,
1222
- Markdown,
1223
- declareHostPoints,
1224
- ThemeProvider,
1225
- useTheme,
1226
- ThemeSwitcher,
1227
- AppMenuBar,
1228
- SearchBox,
1229
- NotificationBell,
1230
- ToastHost,
1231
- TitleBar
1232
- };