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
@@ -57,6 +57,7 @@ import {
57
57
  normalizePath,
58
58
  parseOrThrow,
59
59
  parseVersion,
60
+ parseVideo,
60
61
  pluginApi,
61
62
  preloadMember,
62
63
  routePatternMatches,
@@ -68,7 +69,7 @@ import {
68
69
  validateManifest,
69
70
  videoEmbedUrl,
70
71
  withTimeout
71
- } from "./chunks/chunk-EVX2O65A.js";
72
+ } from "./chunks/chunk-SDCGUBEW.js";
72
73
  import "./chunks/chunk-WSFCRVEQ.js";
73
74
  export {
74
75
  BUILTIN_PERMISSIONS,
@@ -129,6 +130,7 @@ export {
129
130
  normalizePath,
130
131
  parseOrThrow,
131
132
  parseVersion,
133
+ parseVideo,
132
134
  pluginApi,
133
135
  preloadMember,
134
136
  routePatternMatches,
package/dist/node/main.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  JsonStateFile,
9
9
  LocalRegistry,
10
10
  MANIFEST_FILE,
11
+ MIME,
11
12
  MainFramework,
12
13
  PLUGIN_SCHEME,
13
14
  PluginInstaller,
@@ -21,11 +22,13 @@ import {
21
22
  createRegistries,
22
23
  createTarGz,
23
24
  discoverPlugins,
25
+ downloadFolder,
24
26
  entriesFromIndex,
25
27
  extractTarGz,
26
28
  generateSigningKeyPair,
27
29
  importFresh,
28
30
  pruneNonRuntime,
31
+ readAsset,
29
32
  readPluginDir,
30
33
  readPluginManifest,
31
34
  readTarGz,
@@ -35,8 +38,8 @@ import {
35
38
  sha256File,
36
39
  signManifest,
37
40
  verifyPlugin
38
- } from "./chunks/chunk-C7VWZZOX.js";
39
- import "./chunks/chunk-EVX2O65A.js";
41
+ } from "./chunks/chunk-ZLA5XUXN.js";
42
+ import "./chunks/chunk-SDCGUBEW.js";
40
43
  import "./chunks/chunk-WSFCRVEQ.js";
41
44
  export {
42
45
  DEFAULT_STORE,
@@ -48,6 +51,7 @@ export {
48
51
  JsonStateFile,
49
52
  LocalRegistry,
50
53
  MANIFEST_FILE,
54
+ MIME,
51
55
  MainFramework,
52
56
  PLUGIN_SCHEME,
53
57
  PluginInstaller,
@@ -61,11 +65,13 @@ export {
61
65
  createRegistries,
62
66
  createTarGz,
63
67
  discoverPlugins,
68
+ downloadFolder,
64
69
  entriesFromIndex,
65
70
  extractTarGz,
66
71
  generateSigningKeyPair,
67
72
  importFresh,
68
73
  pruneNonRuntime,
74
+ readAsset,
69
75
  readPluginDir,
70
76
  readPluginManifest,
71
77
  readTarGz,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CHANNELS
3
- } from "./chunks/chunk-EVX2O65A.js";
3
+ } from "./chunks/chunk-SDCGUBEW.js";
4
4
  import "./chunks/chunk-WSFCRVEQ.js";
5
5
 
6
6
  // src/preload/index.ts
@@ -5,3 +5,8 @@ export declare function buildPlugin(root: string, watch?: boolean, log?: {
5
5
  (...data: any[]): void;
6
6
  (message?: any, ...optionalParams: any[]): void;
7
7
  }): Promise<() => Promise<void>>;
8
+ /**
9
+ * Keeps `<host>/plugins/<id>` in sync with this plugin project (runtime files only): lets you develop a plugin as its own
10
+ * project and watch it run in the main app (which hot-reloads it). Returns a function that stops syncing.
11
+ */
12
+ export declare function linkPluginToHost(root: string, hostDir: string, log?: (m: string) => void): Promise<() => void>;
@@ -1,2 +1,4 @@
1
1
  export declare function readManifest(root: string): Promise<Record<string, unknown>>;
2
2
  export declare function run(argv: string[]): Promise<number>;
3
+ /** "github:owner/repo" | "https://host/plugins.json" (JSON link) | "https://host/store/" (folder with index.json) */
4
+ export declare function parseStoreSpec(spec: string | undefined): import("../main/index.js").StoreSpec[] | undefined;
@@ -0,0 +1,18 @@
1
+ /** Copies every plugin's runtime files (no src, no .ts, no sourcemaps) to `outDir/<folder>`. Returns the plugin count. */
2
+ export declare function stagePlugins(cwd: string, outDir: string, log?: (m: string) => void): Promise<number>;
3
+ /** Builds the app and packages it into an installer/executable with electron-builder (current OS by default). */
4
+ export declare function distApp(cwd: string, opts?: {
5
+ win?: boolean;
6
+ mac?: boolean;
7
+ linux?: boolean;
8
+ dir?: boolean;
9
+ log?: (m: string) => void;
10
+ }): Promise<number>;
11
+ /**
12
+ * Extracts the app as a static website: the built renderer, every plugin's runtime files and a `bootstrap.json` describing them.
13
+ * Serve the folder with any static host. Only the browser side runs (no main-process code): renderer-only plugins work fully.
14
+ */
15
+ export declare function exportWebsite(cwd: string, outDir: string, log?: (m: string) => void): Promise<{
16
+ plugins: number;
17
+ out: string;
18
+ }>;
@@ -4,6 +4,8 @@ export interface HostVars {
4
4
  /** Electron semver range (init resolves the latest published version). */
5
5
  electron?: string;
6
6
  tailwind?: boolean;
7
+ /** Stores for fluxplugin.config.json (default: the GitHub topic store). */
8
+ stores?: import("../main/index.js").StoreSpec[];
7
9
  }
8
10
  /** Files of a new host application (`fluxplugin init`). Plain strings so the CLI stays a single self-contained file. */
9
11
  export declare function hostFiles(v: HostVars, withSample?: boolean): Record<string, string>;
@@ -5,3 +5,16 @@ export declare function storeAdd(pkgFile: string, dir: string, baseUrl?: string)
5
5
  version: string;
6
6
  sha256: string;
7
7
  }>;
8
+ /**
9
+ * Builds the catalog JSON from a repository of plugin folders (`<dir>/plugins/<name>/plugin.json` + built `dist/`).
10
+ * One JSON holds every plugin's metadata (icon, screenshots, video, README…), the exact list of runtime files to download
11
+ * (sources and sourcemaps are never listed) and the sha256 of each file. Commit it next to the folders.
12
+ */
13
+ export declare function storeBuild(dir: string, opts?: {
14
+ out?: string;
15
+ pluginsDir?: string;
16
+ log?: (m: string) => void;
17
+ }): Promise<{
18
+ count: number;
19
+ file: string;
20
+ }>;
@@ -10,3 +10,5 @@ export declare const TEMPLATES: Record<TemplateName, (v: TemplateVars) => Record
10
10
  export declare function varsFromName(name: string): TemplateVars;
11
11
  /** Adds Tailwind v4 to a plugin: utilities only (no preflight, so the host's styles are never reset). */
12
12
  export declare function withTailwind(files: Record<string, string>, v: TemplateVars): Record<string, string>;
13
+ /** Extra files when a plugin is its own project (outside an app, or created with --standalone). */
14
+ export declare function standaloneExtras(v: TemplateVars): Record<string, string>;
@@ -93,5 +93,19 @@ export interface ManifestValidation {
93
93
  export declare function validateManifest(raw: unknown, opts?: {
94
94
  frameworkVersion?: string;
95
95
  }): ManifestValidation;
96
- /** Turns a YouTube/Vimeo URL into a privacy-friendly embed URL; returns undefined for any other host. */
96
+ export type VideoKind = "youtube" | "vimeo" | "dailymotion" | "loom" | "streamable" | "html5" | "link";
97
+ export interface VideoInfo {
98
+ kind: VideoKind;
99
+ url: string;
100
+ /** iframe URL for hosted players. */
101
+ embedUrl?: string;
102
+ /** Direct media URL for the HTML5 <video> player (also a plugin-relative path before it is resolved). */
103
+ src?: string;
104
+ }
105
+ /**
106
+ * Classifies a video reference: hosted players that must be embedded in an iframe (YouTube, Vimeo, Dailymotion, Loom,
107
+ * Streamable), direct media files for the HTML5 player (https URL or a path inside the plugin), or a plain link.
108
+ */
109
+ export declare function parseVideo(url: string | undefined): VideoInfo | undefined;
110
+ /** Back-compat helper: the iframe URL of a hosted player, if any. */
97
111
  export declare function videoEmbedUrl(url: string | undefined): string | undefined;
@@ -86,7 +86,21 @@ export interface RegistryEntryLike {
86
86
  sha256?: string;
87
87
  homepage?: string;
88
88
  stars?: number;
89
- icon?: string;
89
+ /** Store metadata (everything a "store page" needs, straight from the catalog). */
90
+ iconUrl?: string;
91
+ bannerUrl?: string;
92
+ screenshots?: string[];
93
+ video?: string;
94
+ categories?: string[];
95
+ keywords?: string[];
96
+ links?: Record<string, string>;
97
+ license?: string;
98
+ /** Markdown (inline in the catalog). */
99
+ readme?: string;
100
+ /** Set by the app when the plugin is already installed locally. */
101
+ installedVersion?: string;
102
+ folder?: boolean;
103
+ files?: string[];
90
104
  }
91
105
  /** Everything a UI needs to render a plugin "store page": icon, banner, screenshots, video, long description. */
92
106
  export interface PluginDetails {
@@ -104,10 +118,9 @@ export interface PluginDetails {
104
118
  iconUrl?: string;
105
119
  bannerUrl?: string;
106
120
  screenshotUrls: string[];
107
- video?: {
108
- url: string;
109
- embedUrl?: string;
110
- };
121
+ video?: import("./manifest.js").VideoInfo;
122
+ /** Base URL of the plugin's files (to resolve relative images/videos). */
123
+ assetBase?: string;
111
124
  /** Markdown source of the long description (rendered by the UI without raw HTML). */
112
125
  readme?: string;
113
126
  }
@@ -66,10 +66,21 @@ export interface MenuItemDefinition {
66
66
  badge?: string | number;
67
67
  /** Route path, for sidebar entries that navigate without a command. */
68
68
  path?: string;
69
+ /** Id of another item (same location) this one is nested under: builds sub-menus without the parent knowing its children. */
70
+ parent?: string;
69
71
  }
72
+ export type MenuNode = Omit<MenuItemDefinition, "location" | "children"> & {
73
+ location?: MenuItemDefinition["location"];
74
+ children?: MenuNode[];
75
+ };
70
76
  export declare class MenuRegistry extends Registry<MenuItemDefinition> {
71
77
  constructor();
72
78
  register(item: MenuItemDefinition, owner?: string): Disposable;
79
+ /**
80
+ * Hierarchical view of a location: items that name a `parent`, plus the parent's own `children`, are nested under it
81
+ * (sorted by order). Unknown parents fall back to top level, so a missing plugin never hides its siblings.
82
+ */
83
+ tree(location: MenuLocation): MenuNode[];
73
84
  items(location: MenuLocation): MenuItemDefinition[];
74
85
  }
75
86
  export interface ServiceEntry<T = unknown> {
@@ -21,7 +21,9 @@ export interface RouteMeta {
21
21
  location?: MenuLocation;
22
22
  label?: string;
23
23
  order?: number;
24
- group?: string;
24
+ group?: string; /** nest under this menu item id */
25
+ parent?: string;
26
+ icon?: unknown;
25
27
  };
26
28
  [key: string]: unknown;
27
29
  }
@@ -54,6 +54,9 @@ export declare function resolvePluginAsset(fw: MainFramework, url: string): Prom
54
54
  export declare function registerPluginProtocol(fw: MainFramework, protocol: {
55
55
  handle(scheme: string, handler: (req: {
56
56
  url: string;
57
+ headers?: {
58
+ get(name: string): string | null;
59
+ };
57
60
  }) => Promise<Response> | Response): void;
58
61
  }): void;
59
62
  export interface HttpAdapterOptions {
@@ -0,0 +1,8 @@
1
+ export declare const MIME: Record<string, string>;
2
+ export interface AssetResponse {
3
+ status: 200 | 206 | 416;
4
+ headers: Record<string, string>;
5
+ body: Buffer;
6
+ }
7
+ /** Serves a file, honouring `Range` so <video> can seek. Reads only the requested slice. */
8
+ export declare function readAsset(file: string, mime: string, range?: string | null): Promise<AssetResponse>;
@@ -30,7 +30,8 @@ export interface PluginDescriptor {
30
30
  * GitHub as a plugin store. Two modes (they can be combined by adding two registries):
31
31
  * - `topic` (default): developers publish a GitHub *release* in their own repo with `fluxplugin publish`; it is discovered
32
32
  * through the repository topic. No central approval.
33
- * - `repo`: a curated catalog repository with an `index.json`.
33
+ * - `repo`: a catalog repository: plugin folders + one JSON file (`index.json` by default, see `fluxplugin store build`) with all the
34
+ * metadata. Plugins are installed straight from their folders in the repo (every file verified against the catalog hashes).
34
35
  */
35
36
  export declare class GitHubRegistry implements PluginRegistry {
36
37
  private readonly o;
@@ -8,3 +8,4 @@ export * from "./tar.js";
8
8
  export * from "./serve-web.js";
9
9
  export * from "./github-registry.js";
10
10
  export * from "./stores.js";
11
+ export * from "./assets.js";
@@ -14,7 +14,21 @@ export interface RegistryEntry {
14
14
  sha256?: string;
15
15
  homepage?: string;
16
16
  stars?: number;
17
- icon?: string;
17
+ iconUrl?: string;
18
+ bannerUrl?: string;
19
+ screenshots?: string[];
20
+ video?: string;
21
+ categories?: string[];
22
+ keywords?: string[];
23
+ links?: Record<string, string>;
24
+ license?: string;
25
+ readme?: string;
26
+ /** Filled in by the app: version already installed locally. */
27
+ installedVersion?: string;
28
+ /** Folder entry: `location` is a folder URL; download `files` and verify them against `hashes`. */
29
+ folder?: boolean;
30
+ files?: string[];
31
+ hashes?: Record<string, string>;
18
32
  }
19
33
  /**
20
34
  * Abstraction over where plugins come from. Implement it for npm, GitHub releases, a private HTTP registry,
@@ -36,22 +50,39 @@ export declare function readPluginManifest(dir: string): Promise<{
36
50
  */
37
51
  export declare function copyPluginRuntime(src: string, dest: string): Promise<string[]>;
38
52
  export declare function copyDir(src: string, dest: string): Promise<void>;
39
- /** Shape of a store catalog (`index.json`). Used by HTTP stores, GitHub catalog stores and `fluxplugin store add`. */
53
+ /** Shape of a store catalog: `{ "plugins": [...] }` or a bare array. Served as a file, built by `fluxplugin store build`, or returned by any API. */
54
+ export interface StoreIndexPlugin {
55
+ id: string;
56
+ name?: string;
57
+ version: string;
58
+ description?: string;
59
+ author?: string;
60
+ permissions?: string[];
61
+ /** A package (.tgz)… */
62
+ url?: string;
63
+ sha256?: string;
64
+ /** …or a plugin folder in the repository / on the host, with its file list and sha256 of every file. */
65
+ folder?: string;
66
+ files?: string[];
67
+ hashes?: Record<string, string>;
68
+ homepage?: string;
69
+ license?: string;
70
+ icon?: string;
71
+ banner?: string;
72
+ screenshots?: string[];
73
+ video?: string;
74
+ categories?: string[];
75
+ keywords?: string[];
76
+ links?: Record<string, string>;
77
+ /** Markdown, inline (keep it short) */
78
+ readme?: string;
79
+ }
40
80
  export interface StoreIndex {
41
- plugins: {
42
- id: string;
43
- name?: string;
44
- version: string;
45
- description?: string;
46
- author?: string;
47
- permissions?: string[];
48
- url: string;
49
- sha256?: string;
50
- homepage?: string;
51
- icon?: string;
52
- }[];
81
+ plugins: StoreIndexPlugin[];
53
82
  }
54
- export declare function entriesFromIndex(index: StoreIndex, baseUrl: string, source: string, query?: string): RegistryEntry[];
83
+ export declare function entriesFromIndex(index: StoreIndex | StoreIndexPlugin[], baseUrl: string, source: string, query?: string): RegistryEntry[];
84
+ /** Downloads a plugin folder file by file, verifying every file against the catalog's sha256 (required). */
85
+ export declare function downloadFolder(entry: RegistryEntry, dest: string, get: (url: string) => Promise<Response>): Promise<void>;
55
86
  /** Removes everything that is not runtime code from an extracted plugin: sources, TypeScript, sourcemaps, node_modules. */
56
87
  export declare function pruneNonRuntime(dir: string): Promise<string[]>;
57
88
  /** A directory whose sub-directories are plugins (e.g. a shared network folder or a repo checkout). */
@@ -63,14 +94,20 @@ export declare class LocalRegistry implements PluginRegistry {
63
94
  fetch(entry: RegistryEntry, dest: string): Promise<PluginManifest>;
64
95
  }
65
96
  /**
66
- * HTTP registry: GET `<baseUrl>/index.json` → `{ plugins: [{ id, name, version, description, author, permissions, url, sha256 }] }`
67
- * where `url` points at a `.tgz` produced by `fluxplugin package`. Downloads are verified against `sha256` when given.
97
+ * HTTP/JSON store. `url` is either a folder holding `index.json` ("https://plugins.acme.com/") or a direct link to a JSON
98
+ * document / API endpoint that returns the catalog ("https://api.acme.com/plugins" or ".../plugins.json").
99
+ * Entries are packages (`url` → .tgz, checked against `sha256`) or plugin folders (`folder` + `files` + `hashes`).
68
100
  */
69
101
  export declare class HttpRegistry implements PluginRegistry {
70
102
  private readonly baseUrl;
71
103
  private readonly fetchImpl;
104
+ private readonly headers;
105
+ private readonly direct;
72
106
  readonly name: string;
73
- constructor(baseUrl: string, fetchImpl?: typeof fetch);
107
+ constructor(baseUrl: string, fetchImpl?: typeof fetch, headers?: Record<string, string>, direct?: boolean);
108
+ /** Direct link (".json" file, or `direct: true` for an API endpoint) or a folder that contains index.json. */
109
+ private get catalogUrl();
110
+ private get;
74
111
  search(query?: string): Promise<RegistryEntry[]>;
75
112
  fetch(entry: RegistryEntry, dest: string): Promise<PluginManifest>;
76
113
  }
@@ -3,7 +3,8 @@ import { type PluginRegistry } from "./registry.js";
3
3
  * A plugin store, as written in `fluxplugin.config.json` → `"stores"` (or passed to `MainFramework`).
4
4
  * - `github` + `topic`: every repo with that topic that published a release with `fluxplugin publish` (default store)
5
5
  * - `github` + `repo`: a curated catalog repository ("owner/name") containing `index.json`
6
- * - `http`: any static host serving `index.json` + `.tgz` files (GitHub Pages, S3, your server)
6
+ * - `http`: a static host serving `index.json` + packages/folders (GitHub Pages, S3, your server)
7
+ * - `json`: one URL that returns the catalog as JSON (a file, or your own API); same format as index.json
7
8
  * - `local`: a folder (network share, checkout)
8
9
  */
9
10
  export type StoreSpec = {
@@ -16,6 +17,11 @@ export type StoreSpec = {
16
17
  } | {
17
18
  type: "http";
18
19
  url: string;
20
+ headers?: Record<string, string>;
21
+ } | {
22
+ type: "json";
23
+ url: string;
24
+ headers?: Record<string, string>;
19
25
  } | {
20
26
  type: "local";
21
27
  path: string;
@@ -1,7 +1,39 @@
1
1
  import React from "react";
2
- /** Auto-generated settings form from a plugin's config schema (`ctx.config.describe()`). */
2
+ import type { VideoInfo } from "../core/index.js";
3
+ import { type RendererFramework } from "../react/index.js";
4
+ /** Everything the "app page" of a plugin needs, whether it is installed or only listed in a store. */
5
+ export interface PluginView {
6
+ id: string;
7
+ name: string;
8
+ version: string;
9
+ description?: string;
10
+ author?: string;
11
+ license?: string;
12
+ iconUrl?: string;
13
+ bannerUrl?: string;
14
+ screenshots: string[];
15
+ video?: VideoInfo;
16
+ categories: string[];
17
+ keywords: string[];
18
+ links: Record<string, string>;
19
+ readme?: string;
20
+ permissions: string[];
21
+ dependencies: Record<string, string>;
22
+ /** Base URL used to resolve relative images in the README. */
23
+ assetBase?: string;
24
+ homepage?: string;
25
+ }
26
+ /** Auto-generated settings form from the plugin's config schema. */
3
27
  export declare function ConfigForm({ id }: {
4
28
  id: string;
5
29
  }): React.ReactElement | null;
6
- /** Full plugin manager screen. Extensible through the `manager.plugin.actions` / `manager.plugin.details` slots. */
7
- export declare function PluginManagerUI(): React.ReactElement;
30
+ /** Detail screen of an installed plugin (route `/plugins/:id`). */
31
+ export declare function PluginDetailsPage(): React.ReactElement;
32
+ /** Detail screen of a plugin listed in a store (route `/plugins/store/:id`). */
33
+ export declare function StorePluginPage(): React.ReactElement;
34
+ /** The plugin list: Installed · Discover (stores) · Updates. Rows open the detail screens. */
35
+ export declare function PluginsPage(): React.ReactElement;
36
+ /** Registers `/plugins`, `/plugins/:id` and `/plugins/store/:id` with their breadcrumbs. */
37
+ export declare function registerPluginManagerRoutes(framework: RendererFramework): void;
38
+ /** Back-compat: the list page. */
39
+ export declare const PluginManagerUI: typeof PluginsPage;
@@ -1,2 +1,6 @@
1
- /** Minimal CSS for the framework's own components. Uses CSS variables so hosts can theme it; Tailwind is not required. */
2
- export declare const baseCss = "\n: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}\n@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}}\n.fluxplugin-error{padding:8px 12px;border:1px solid var(--flux-danger);border-radius:var(--flux-radius);color:var(--flux-danger);font-size:13px}\n.flux-breadcrumbs{font-size:13px;color:var(--flux-muted)}.flux-breadcrumbs a{color:inherit}\n.flux-not-found,.flux-forbidden,.flux-loading{padding:24px;color:var(--flux-muted)}\n.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}\n.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}\n.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}\n.flux-palette-list{list-style:none;margin:0;padding:6px;max-height:340px;overflow:auto}\n.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}\n.flux-palette-item.is-active{background:var(--flux-accent);color:var(--flux-accent-fg)}\n.flux-palette-cat{opacity:.65}.flux-palette-empty{padding:14px;color:var(--flux-muted);font-size:14px}\n.flux-palette kbd{font:11px ui-monospace,monospace;padding:2px 6px;border:1px solid currentColor;border-radius:5px;opacity:.7}\n\n:root[data-theme=\"dark\"]{--flux-bg:#0b1020;--flux-fg:#e5e7eb;--flux-muted:#94a3b8;--flux-border:#1f2a44;--flux-surface:#111936}\n:root[data-theme=\"light\"]{--flux-bg:#fff;--flux-fg:#0f172a;--flux-muted:#64748b;--flux-border:#e2e8f0;--flux-surface:#f8fafc}\n.flux-icon{flex:none;vertical-align:middle}\n.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}\n.flux-titlebar[data-electron][data-os=\"mac\"]{padding-left:82px}\n.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)}\n.flux-titlebar button,.flux-titlebar input,.flux-titlebar select,.flux-titlebar .flux-dropdown{-webkit-app-region:no-drag}\n.flux-tb-left,.flux-tb-right{display:flex;align-items:center;gap:4px;min-width:0}.flux-tb-right{justify-content:flex-end}\n.flux-brand{display:flex;align-items:center;gap:8px;font-weight:600;margin-right:8px;white-space:nowrap}\n.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}\n.flux-tb-btn:hover,.flux-tb-btn.is-open{background:var(--flux-border)}\n.flux-menubar{display:flex}.flux-menu{position:relative}\n.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}\n.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}\n.flux-dd-item:hover,.flux-dd-item.is-active{background:var(--flux-accent);color:var(--flux-accent-fg)}\n.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}\n.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)}\n.flux-search input{flex:1;min-width:0;border:0;background:transparent;color:var(--flux-fg);font:inherit;outline:none}\n.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}\n.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}\n.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}\n.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)}\n.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)}\n.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)}\n.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}\n.flux-link{background:none;border:0;color:var(--flux-accent);cursor:pointer;font:inherit;font-size:12px;padding:0}\n.flux-toasts{position:fixed;top:48px;right:12px;display:flex;flex-direction:column;gap:8px;z-index:40;pointer-events:none}\n.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}\n.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)}\n.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}\n.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)}\n";
1
+ /**
2
+ * Design system of the framework's components: macOS look (SF-style type, translucent materials, grouped lists, switches,
3
+ * segmented controls). Everything is driven by CSS variables so hosts, users (Settings → Appearance) and plugin themes can
4
+ * restyle it: --flux-font, --flux-font-size, --flux-line, --flux-radius, --flux-accent, --flux-bg/-surface/-fg/-muted/-border …
5
+ */
6
+ export declare const baseCss = "\n:root{--flux-font:-apple-system,BlinkMacSystemFont,\"SF Pro Text\",\"Segoe UI Variable\",\"Segoe UI\",system-ui,Roboto,\"Helvetica Neue\",sans-serif;--flux-font-size:14px;--flux-line:1.5;--flux-radius:10px;--flux-radius-lg:14px;--flux-pad:1;\n--flux-bg:#fff;--flux-surface:#f5f5f7;--flux-surface-2:#e9e9ee;--flux-fg:#1d1d1f;--flux-muted:#6e6e73;--flux-border:rgba(60,60,67,.16);--flux-accent:#007aff;--flux-accent-fg:#fff;--flux-ok:#34c759;--flux-warn:#ff9f0a;--flux-danger:#ff3b30;--flux-shadow:0 10px 34px rgba(0,0,0,.14);--flux-material:rgba(246,246,248,.78)}\n@media (prefers-color-scheme:dark){:root:not([data-theme]){--flux-bg:#1c1c1e;--flux-surface:#2c2c2e;--flux-surface-2:#3a3a3c;--flux-fg:#f5f5f7;--flux-muted:#98989d;--flux-border:rgba(235,235,245,.16);--flux-accent:#0a84ff;--flux-shadow:0 10px 34px rgba(0,0,0,.55);--flux-material:rgba(44,44,46,.72)}}\n:root[data-theme=\"dark\"]{--flux-bg:#1c1c1e;--flux-surface:#2c2c2e;--flux-surface-2:#3a3a3c;--flux-fg:#f5f5f7;--flux-muted:#98989d;--flux-border:rgba(235,235,245,.16);--flux-accent:#0a84ff;--flux-shadow:0 10px 34px rgba(0,0,0,.55);--flux-material:rgba(44,44,46,.72)}\n:root[data-theme=\"light\"]{--flux-bg:#fff;--flux-surface:#f5f5f7;--flux-surface-2:#e9e9ee;--flux-fg:#1d1d1f;--flux-muted:#6e6e73;--flux-border:rgba(60,60,67,.16);--flux-accent:#007aff;--flux-material:rgba(246,246,248,.78)}\n:root[data-radius=\"sharp\"]{--flux-radius:4px;--flux-radius-lg:6px}:root[data-radius=\"round\"]{--flux-radius:14px;--flux-radius-lg:22px}:root[data-density=\"compact\"]{--flux-pad:.7}\n:root[data-motion=\"reduced\"] *{transition:none!important;animation:none!important}\nhtml{font-family:var(--flux-font);font-size:var(--flux-font-size);line-height:var(--flux-line);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}\nbody{font-family:var(--flux-font);font-size:1em;line-height:var(--flux-line);color:var(--flux-fg);background:var(--flux-bg)}\nbutton,input,select,textarea{font-family:inherit;font-size:inherit}\n.flux-icon{flex:none;vertical-align:middle}\n.flux-large-title{font-size:2em;font-weight:700;letter-spacing:-.025em;margin:0 0 .6em;line-height:1.15}.flux-title{font-size:1.5em;font-weight:700;letter-spacing:-.02em;margin:0 0 .8em}\n.flux-empty,.flux-not-found,.flux-forbidden,.flux-loading{padding:24px;color:var(--flux-muted)}\n.flux-plugin-error{padding:10px 14px;border-radius:var(--flux-radius);background:color-mix(in srgb,var(--flux-danger) 12%,transparent);color:var(--flux-danger);font-size:.92em}\n.flux-breadcrumbs{font-size:.86em;color:var(--flux-muted);margin-bottom:6px}.flux-breadcrumbs a{color:inherit;text-decoration:none}.flux-breadcrumbs a:hover{color:var(--flux-accent)}\n/* ---- materials (vibrancy) */\n.flux-titlebar,.flux-side,.flux-dropdown,.flux-toast,.flux-palette{background:var(--flux-material);-webkit-backdrop-filter:saturate(180%) blur(24px);backdrop-filter:saturate(180%) blur(24px)}\n:root[data-translucency=\"off\"] .flux-titlebar,:root[data-translucency=\"off\"] .flux-side,:root[data-translucency=\"off\"] .flux-dropdown,:root[data-translucency=\"off\"] .flux-toast,:root[data-translucency=\"off\"] .flux-palette{background:var(--flux-surface);-webkit-backdrop-filter:none;backdrop-filter:none}\n/* ---- buttons / switch / segmented */\n.flux-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border:1px solid var(--flux-border);background:var(--flux-bg);color:var(--flux-fg);border-radius:calc(var(--flux-radius) - 2px);padding:calc(5px*var(--flux-pad)) 14px;font-weight:500;cursor:pointer;transition:background .12s,transform .06s}\n.flux-btn:hover{background:var(--flux-surface)}.flux-btn:active{transform:scale(.97)}.flux-btn:disabled{opacity:.5;cursor:default}\n.flux-btn-primary{background:var(--flux-accent);border-color:transparent;color:var(--flux-accent-fg)}.flux-btn-primary:hover{background:var(--flux-accent);filter:brightness(1.08)}\n.flux-btn-destructive{color:var(--flux-danger)}.flux-btn-plain{border-color:transparent;background:transparent;color:var(--flux-accent)}.flux-btn-plain:hover{background:var(--flux-surface)}\n.flux-btn-sm{padding:3px 11px;font-size:.92em;border-radius:99px}.flux-btn-lg{padding:9px 22px;font-size:1.05em;border-radius:99px}\n.flux-switch{position:relative;width:42px;height:26px;border-radius:99px;border:0;background:var(--flux-surface-2);cursor:pointer;transition:background .18s;flex:none;padding:0}\n.flux-switch span{position:absolute;top:2px;left:2px;width:22px;height:22px;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.35);transition:transform .18s}\n.flux-switch[data-on]{background:var(--flux-ok)}.flux-switch[data-on] span{transform:translateX(16px)}.flux-switch:focus-visible{outline:2px solid var(--flux-accent);outline-offset:2px}\n.flux-seg{display:inline-flex;background:var(--flux-surface-2);border-radius:9px;padding:2px;gap:2px}\n.flux-seg button{border:0;background:transparent;color:var(--flux-fg);border-radius:7px;padding:calc(4px*var(--flux-pad)) 14px;font-weight:500;cursor:pointer}\n.flux-seg button[data-on]{background:var(--flux-bg);box-shadow:0 1px 3px rgba(0,0,0,.18)}\n.flux-input{width:100%;box-sizing:border-box;border:1px solid var(--flux-border);background:var(--flux-bg);color:var(--flux-fg);border-radius:var(--flux-radius);padding:calc(7px*var(--flux-pad)) 12px;outline:none}.flux-input:focus{border-color:var(--flux-accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--flux-accent) 25%,transparent)}\n.flux-select{background:var(--flux-bg);color:inherit;border:1px solid var(--flux-border);border-radius:7px;padding:3px 6px}\n/* ---- grouped lists */\n.flux-group-box{margin:0 0 calc(22px*var(--flux-pad))}.flux-group-title{font-size:.78em;text-transform:uppercase;letter-spacing:.06em;color:var(--flux-muted);margin:0 14px 6px;font-weight:600}\n.flux-group-body{background:var(--flux-surface);border-radius:var(--flux-radius-lg);overflow:hidden}.flux-group-footer{font-size:.82em;color:var(--flux-muted);margin:6px 14px 0}\n.flux-row{display:flex;align-items:center;gap:12px;width:100%;box-sizing:border-box;padding:calc(10px*var(--flux-pad)) 14px;border:0;background:transparent;color:inherit;text-align:left;position:relative;min-height:calc(44px*var(--flux-pad))}\n.flux-row+.flux-row::before{content:\"\";position:absolute;top:0;left:14px;right:0;height:1px;background:var(--flux-border)}\n.flux-row.is-link{cursor:pointer}.flux-row.is-link:hover{background:color-mix(in srgb,var(--flux-fg) 5%,transparent)}.flux-row[data-selected]{background:color-mix(in srgb,var(--flux-accent) 14%,transparent)}\n.flux-row-icon{width:28px;height:28px;border-radius:7px;display:inline-flex;align-items:center;justify-content:center;background:var(--flux-accent);color:#fff;flex:none;overflow:hidden}\n.flux-row-text{display:flex;flex-direction:column;min-width:0;flex:1}.flux-row-title{font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.flux-row-sub{font-size:.88em;color:var(--flux-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.flux-row-right{display:flex;align-items:center;gap:8px;color:var(--flux-muted)}\n.flux-img-fallback{display:inline-flex;align-items:center;justify-content:center;background:var(--flux-surface-2);color:var(--flux-muted);border-radius:inherit;min-width:32px;min-height:32px}\n/* ---- title bar */\n.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 10px;border-bottom:1px solid var(--flux-border);color:var(--flux-fg);-webkit-app-region:drag;user-select:none;position:relative;z-index:50}\n.flux-titlebar[data-electron][data-os=\"mac\"]{padding-left:82px}\n.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)}\n.flux-titlebar button,.flux-titlebar input,.flux-titlebar select,.flux-titlebar .flux-dropdown{-webkit-app-region:no-drag}\n.flux-tb-left,.flux-tb-right{display:flex;align-items:center;gap:2px;min-width:0}.flux-tb-right{justify-content:flex-end}\n.flux-brand{display:flex;align-items:center;gap:8px;font-weight:650;margin-right:8px;white-space:nowrap}\n.flux-tb-btn{display:inline-flex;align-items:center;gap:6px;border:0;background:transparent;color:inherit;border-radius:6px;padding:4px 9px;font:inherit;cursor:pointer;position:relative}\n.flux-tb-btn:hover,.flux-tb-btn.is-open{background:color-mix(in srgb,var(--flux-fg) 9%,transparent)}\n.flux-menubar{display:flex}.flux-menu{position:relative}\n.flux-dropdown{position:absolute;top:calc(100% + 4px);left:0;min-width:230px;max-width:380px;margin:0;padding:5px;list-style:none;color:var(--flux-fg);border:1px solid var(--flux-border);border-radius:10px;box-shadow:var(--flux-shadow);z-index:60}\n.flux-dd-item{display:flex;justify-content:space-between;align-items:center;gap:18px;width:100%;border:0;background:transparent;color:inherit;font:inherit;text-align:left;padding:5px 10px;border-radius:5px;cursor:pointer}\n.flux-dd-item:hover,.flux-dd-item.is-active{background:var(--flux-accent);color:var(--flux-accent-fg)}.flux-dd-item kbd,.flux-dd-item small{opacity:.6;font:11px ui-monospace,monospace}.flux-sep{height:1px;background:var(--flux-border);margin:5px 6px}\n.flux-search{position:relative;display:flex;align-items:center;gap:8px;background:color-mix(in srgb,var(--flux-fg) 7%,transparent);border-radius:8px;padding:0 10px;height:26px;color:var(--flux-muted)}\n.flux-search input{flex:1;min-width:0;border:0;background:transparent;color:var(--flux-fg);outline:none}.flux-search:focus-within{box-shadow:0 0 0 3px color-mix(in srgb,var(--flux-accent) 30%,transparent)}\n.flux-search-results{left:0;right:0;max-width:none;top:calc(100% + 6px)}.flux-group{font-size:.74em;text-transform:uppercase;letter-spacing:.06em;color:var(--flux-muted);padding:7px 10px 2px;font-weight:600}\n.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}\n.flux-notif-panel{left:auto;right:0;width:350px;max-height:440px;overflow:auto;padding:0}.flux-notif-head{display:flex;justify-content:space-between;padding:12px 14px;border-bottom:1px solid var(--flux-border)}\n.flux-notif{display:flex;justify-content:space-between;gap:10px;padding:11px 14px;border-bottom:1px solid var(--flux-border)}.flux-notif p{margin:2px 0 4px;font-size:.92em}.flux-notif small{color:var(--flux-muted)}.flux-notif-actions{display:flex;flex-direction:column;align-items:flex-end;gap:4px}\n.flux-notif.flux-success{box-shadow:inset 3px 0 var(--flux-ok)}.flux-notif.flux-warning{box-shadow:inset 3px 0 var(--flux-warn)}.flux-notif.flux-error{box-shadow:inset 3px 0 var(--flux-danger)}.flux-notif.flux-info{box-shadow:inset 3px 0 var(--flux-accent)}\n.flux-link{background:none;border:0;color:var(--flux-accent);cursor:pointer;font:inherit;font-size:.88em;padding:0}\n.flux-toasts{position:fixed;top:48px;right:14px;display:flex;flex-direction:column;gap:8px;z-index:40;pointer-events:none}\n.flux-toast{pointer-events:auto;color:var(--flux-fg);border:1px solid var(--flux-border);border-radius:14px;padding:11px 15px;box-shadow:var(--flux-shadow);max-width:350px;font-size:.93em}\n.flux-toast.flux-success{border-left:4px solid var(--flux-ok)}.flux-toast.flux-warning{border-left:4px solid var(--flux-warn)}.flux-toast.flux-error{border-left:4px solid var(--flux-danger)}\n/* ---- command palette (Spotlight) */\n.flux-palette-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.28);display:flex;justify-content:center;align-items:flex-start;padding-top:14vh;z-index:9999}\n.flux-palette{width:min(600px,92vw);color:var(--flux-fg);border:1px solid var(--flux-border);border-radius:16px;box-shadow:var(--flux-shadow);overflow:hidden}\n.flux-palette-input{width:100%;box-sizing:border-box;padding:16px 18px;border:0;border-bottom:1px solid var(--flux-border);background:transparent;color:inherit;font-size:1.25em;outline:none}\n.flux-palette-list{list-style:none;margin:0;padding:6px;max-height:360px;overflow:auto}.flux-palette-item{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:8px 12px;border-radius:8px;cursor:pointer}\n.flux-palette-item.is-active{background:var(--flux-accent);color:var(--flux-accent-fg)}.flux-palette-cat{opacity:.65}.flux-palette-empty{padding:16px;color:var(--flux-muted)}.flux-palette kbd{font:11px ui-monospace,monospace;opacity:.7}\n/* ---- sidebar */\n.flux-side{display:flex;flex-direction:column;justify-content:space-between;height:100%;box-sizing:border-box;padding:12px 10px;border-right:1px solid var(--flux-border);overflow-y:auto;overflow-x:hidden;transition:padding .18s}\n.flux-side-nav,.flux-side-bottom{display:flex;flex-direction:column;gap:2px}.flux-side-sep{height:1px;background:var(--flux-border);margin:8px 8px}\n.flux-nav-group{position:relative}\n.flux-nav{display:flex;align-items:center;gap:10px;width:100%;box-sizing:border-box;padding:calc(6px*var(--flux-pad)) 10px;border:0;background:transparent;color:inherit;text-decoration:none;border-radius:8px;cursor:pointer;font-weight:500;text-align:left;white-space:nowrap}\n.flux-nav:hover{background:color-mix(in srgb,var(--flux-fg) 7%,transparent)}.flux-nav[data-active]{background:var(--flux-accent);color:var(--flux-accent-fg)}.flux-nav[data-parent-active]{font-weight:650}\n.flux-nav-icon{display:inline-flex;width:22px;justify-content:center;flex:none}.flux-nav-label{flex:1;overflow:hidden;text-overflow:ellipsis}\n.flux-nav-badge{font-size:.78em;background:var(--flux-danger);color:#fff;border-radius:99px;padding:0 6px}.flux-nav-chev{display:inline-flex;transition:transform .15s;opacity:.6}.flux-nav-chev[data-open]{transform:rotate(90deg)}\n.flux-subnav{margin-left:14px;padding-left:8px;border-left:1px solid var(--flux-border);display:flex;flex-direction:column;gap:2px;margin-top:2px}.flux-subnav .flux-nav{font-weight:450;padding-top:calc(4px*var(--flux-pad));padding-bottom:calc(4px*var(--flux-pad))}\n.flux-side[data-collapsed] .flux-nav{justify-content:center;padding:9px 0}\n.flux-flyout{display:none;position:absolute;left:calc(100% + 6px);top:0;min-width:190px;padding:6px;background:var(--flux-bg);border:1px solid var(--flux-border);border-radius:10px;box-shadow:var(--flux-shadow);z-index:80}.flux-nav-group:hover>.flux-flyout{display:block}\n.flux-flyout-title{font-size:.75em;text-transform:uppercase;letter-spacing:.06em;color:var(--flux-muted);padding:4px 10px}.flux-flyout-item{display:block;padding:6px 10px;border-radius:6px;color:inherit;text-decoration:none}.flux-flyout-item:hover,.flux-flyout-item[data-active]{background:var(--flux-accent);color:var(--flux-accent-fg)}\n.flux-side-footer{font-size:.78em;color:var(--flux-muted);padding:6px 10px}\n/* ---- settings */\n.flux-settings{display:grid;grid-template-columns:240px minmax(0,1fr);gap:28px;align-items:start}.flux-settings-nav{display:flex;flex-direction:column;gap:2px;position:sticky;top:0}\n.flux-settings-item{display:flex;align-items:center;gap:10px;border:0;background:transparent;color:inherit;border-radius:9px;padding:calc(7px*var(--flux-pad)) 10px;cursor:pointer;text-align:left;font-weight:500}.flux-settings-item:hover{background:color-mix(in srgb,var(--flux-fg) 7%,transparent)}.flux-settings-item[data-active]{background:var(--flux-accent);color:var(--flux-accent-fg)}\n.flux-settings-body{min-width:0;max-width:720px}.flux-swatches{display:flex;gap:10px;flex-wrap:wrap}.flux-swatch{width:26px;height:26px;border-radius:50%;border:2px solid transparent;cursor:pointer;box-shadow:inset 0 0 0 2px var(--flux-bg)}.flux-swatch[data-on]{border-color:var(--flux-fg)}\n.flux-theme-card{border:2px solid var(--flux-border);border-radius:12px;overflow:hidden;cursor:pointer;background:none;padding:0;text-align:left;width:128px}.flux-theme-card[data-on]{border-color:var(--flux-accent)}.flux-theme-card .flux-theme-prev{height:62px;display:flex}.flux-theme-card span.cap{display:block;padding:6px 9px;font-size:.84em;font-weight:500}\n.flux-range{width:180px;accent-color:var(--flux-accent)}\n/* ---- plugin pages */\n.flux-pm{max-width:860px;margin:0 auto}.flux-pm-top{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-bottom:14px;flex-wrap:wrap}\n.flux-pm-back{display:inline-flex;align-items:center;gap:2px;border:0;background:none;color:var(--flux-accent);cursor:pointer;font-size:1em;padding:0;margin-bottom:12px}\n.flux-pm-icon{width:56px;height:56px;border-radius:24%;object-fit:cover;flex:none;box-shadow:0 1px 4px rgba(0,0,0,.2);overflow:hidden;display:inline-flex}.flux-pm-icon.lg{width:104px;height:104px;border-radius:23%;box-shadow:0 4px 18px rgba(0,0,0,.22)}\n.flux-pm-hero{display:flex;gap:22px;align-items:center;margin:4px 0 22px;flex-wrap:wrap}.flux-pm-hero h1{margin:0;font-size:2em;letter-spacing:-.02em;line-height:1.15}.flux-pm-hero .by{color:var(--flux-muted);margin:3px 0 12px}.flux-pm-actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap}\n.flux-pm-facts{display:flex;border-top:1px solid var(--flux-border);border-bottom:1px solid var(--flux-border);margin:0 0 22px;overflow:auto}.flux-pm-fact{flex:1;min-width:104px;padding:12px 8px;text-align:center;border-right:1px solid var(--flux-border)}.flux-pm-fact:last-child{border:0}.flux-pm-fact small{display:block;font-size:.7em;letter-spacing:.06em;text-transform:uppercase;color:var(--flux-muted);font-weight:600}.flux-pm-fact b{font-size:1.15em;font-weight:650}\n.flux-pm-shots{display:flex;gap:14px;overflow-x:auto;padding:4px 2px 14px;scroll-snap-type:x mandatory}.flux-pm-shots img,.flux-pm-banner{height:260px;border-radius:var(--flux-radius-lg);border:1px solid var(--flux-border);scroll-snap-align:start;cursor:zoom-in;flex:none}.flux-pm-banner{width:100%;height:auto;max-height:240px;object-fit:cover;cursor:default;margin-bottom:18px}\n.flux-pm h3.sec{font-size:1.3em;font-weight:700;letter-spacing:-.015em;margin:26px 0 10px}\n.flux-pill{display:inline-block;font-size:.76em;padding:2px 9px;border-radius:99px;background:var(--flux-surface-2);color:var(--flux-muted);font-weight:600}.flux-pill.ok{background:color-mix(in srgb,var(--flux-ok) 18%,transparent);color:var(--flux-ok)}.flux-pill.err{background:color-mix(in srgb,var(--flux-danger) 16%,transparent);color:var(--flux-danger)}.flux-pill.warn{background:color-mix(in srgb,var(--flux-warn) 20%,transparent);color:var(--flux-warn)}\n.flux-zoom{position:fixed;inset:0;background:rgba(0,0,0,.82);display:flex;align-items:center;justify-content:center;z-index:9999;cursor:zoom-out}.flux-zoom img{max-width:92vw;max-height:90vh;border-radius:14px}\n.flux-video{width:100%;max-width:640px;aspect-ratio:16/9;border:0;border-radius:var(--flux-radius-lg);background:#000;display:block}.flux-video-link{display:inline-flex;gap:6px;align-items:center;color:var(--flux-accent);text-decoration:none}\n.flux-pre{max-height:200px;overflow:auto;font:12px ui-monospace,monospace;background:var(--flux-surface);padding:12px;border-radius:var(--flux-radius);margin:0;white-space:pre-wrap}\n.flux-fields label{display:flex;gap:12px;align-items:center;justify-content:space-between;padding:10px 14px}\n/* ---- Markdown: app-store style typography */\n.flux-md{font-size:1.04em;line-height:1.68;color:var(--flux-fg);letter-spacing:-.004em;max-width:70ch;overflow-wrap:anywhere}\n.flux-md>:first-child{margin-top:0}.flux-md p{margin:.85em 0}\n.flux-md h2{font-size:1.6em;font-weight:700;letter-spacing:-.022em;line-height:1.2;margin:1.7em 0 .5em}.flux-md h3{font-size:1.28em;font-weight:650;letter-spacing:-.015em;margin:1.5em 0 .4em}.flux-md h4,.flux-md h5{font-size:.82em;font-weight:650;letter-spacing:.07em;text-transform:uppercase;color:var(--flux-muted);margin:1.6em 0 .4em}\n.flux-md a{color:var(--flux-accent);text-decoration:none}.flux-md a:hover{text-decoration:underline;text-underline-offset:3px}\n.flux-md ul,.flux-md ol{padding-left:1.3em;margin:.8em 0}.flux-md li{margin:.4em 0;padding-left:.2em}.flux-md li::marker{color:var(--flux-muted)}.flux-md li.flux-task{list-style:none;margin-left:-1.2em;display:flex;gap:8px;align-items:baseline}\n.flux-md code{font-family:ui-monospace,\"SF Mono\",Menlo,Consolas,monospace;font-size:.86em;background:var(--flux-surface-2);padding:.14em .42em;border-radius:6px}\n.flux-md pre{background:var(--flux-surface);border:1px solid var(--flux-border);border-radius:14px;padding:14px 18px;overflow:auto;line-height:1.55;margin:1.1em 0}.flux-md pre code{background:none;padding:0;font-size:.85em}\n.flux-md blockquote{margin:1.1em 0;padding:.2em 1em;border-left:3px solid var(--flux-border);color:var(--flux-muted)}\n.flux-md hr{border:0;height:1px;background:var(--flux-border);margin:2em 0}\n.flux-md table{width:100%;border-collapse:separate;border-spacing:0;margin:1.2em 0;font-size:.94em;border:1px solid var(--flux-border);border-radius:14px;overflow:hidden}.flux-md th{text-align:left;font-size:.78em;letter-spacing:.06em;text-transform:uppercase;color:var(--flux-muted);background:var(--flux-surface);padding:9px 14px}.flux-md td{padding:10px 14px;border-top:1px solid var(--flux-border)}\n.flux-md-img{max-width:100%;height:auto;border-radius:14px;border:1px solid var(--flux-border);margin:.9em 0;display:block}\n.flux-callout{display:flex;flex-direction:column;gap:2px;margin:1.2em 0;padding:13px 16px;border-radius:14px;background:color-mix(in srgb,var(--c,var(--flux-accent)) 11%,transparent);border-left:4px solid var(--c,var(--flux-accent))}\n.flux-callout strong{color:var(--c,var(--flux-accent));font-size:.82em;letter-spacing:.05em;text-transform:uppercase}.flux-callout[data-kind=\"warning\"],.flux-callout[data-kind=\"caution\"]{--c:var(--flux-warn)}.flux-callout[data-kind=\"tip\"]{--c:var(--flux-ok)}.flux-callout[data-kind=\"important\"]{--c:#af52de}\n";
@@ -8,3 +8,8 @@ export declare class HttpBridgeError extends Error {
8
8
  * (development, headless E2E tests, screenshots). In Electron the preload bridge is used instead.
9
9
  */
10
10
  export declare function createHttpBridge(baseUrl?: string): FluxBridge;
11
+ /**
12
+ * Bridge for a static website export (`fluxplugin export`): reads `bootstrap.json` and has no main process behind it.
13
+ * Renderer-only plugins work; IPC calls reject with a clear message.
14
+ */
15
+ export declare function createStaticBridge(bootstrapUrl?: string): FluxBridge;
@@ -0,0 +1,33 @@
1
+ import React, { type ReactNode } from "react";
2
+ import type { RendererFramework } from "./runtime.js";
3
+ export interface LanguageDef {
4
+ code: string;
5
+ label: string;
6
+ nativeLabel?: string;
7
+ rtl?: boolean;
8
+ }
9
+ export interface MessagesDef {
10
+ locale: string;
11
+ messages: Record<string, string>;
12
+ }
13
+ export type TFunction = (key: string, fallback?: string, params?: Record<string, string | number>) => string;
14
+ interface I18nCtx {
15
+ locale: string;
16
+ setLocale(code: string): void;
17
+ languages: LanguageDef[];
18
+ t: TFunction;
19
+ }
20
+ /** Declares the extension points plugins use to add languages and translations. Called by `declareHostPoints`. */
21
+ export declare function declareI18nPoints(framework: RendererFramework): void;
22
+ /**
23
+ * Translations come from plugins (extension point `i18n`): `languages` lists what the user can pick, `messages` maps keys to text.
24
+ * Components call `useT()("key", "English fallback")`, so an app without any language plugin simply shows the fallback.
25
+ */
26
+ export declare function I18nProvider({ children, defaultLocale, storageKey }: {
27
+ children: ReactNode;
28
+ defaultLocale?: string;
29
+ storageKey?: string;
30
+ }): React.ReactElement;
31
+ export declare const useI18n: () => I18nCtx;
32
+ export declare const useT: () => TFunction;
33
+ export {};
@@ -9,3 +9,8 @@ export * from "./icons.js";
9
9
  export * from "./markdown.js";
10
10
  export * from "./theme.js";
11
11
  export * from "./titlebar.js";
12
+ export * from "./ui.js";
13
+ export * from "./media.js";
14
+ export * from "./i18n.js";
15
+ export * from "./sidebar.js";
16
+ export * from "./settings.js";
@@ -3,6 +3,7 @@ import React from "react";
3
3
  * Small, safe Markdown renderer for plugin descriptions: headings, paragraphs, lists, quotes, code blocks, rules,
4
4
  * bold/italic/inline code and https links. Raw HTML is never interpreted (it is shown as text) and images are not loaded.
5
5
  */
6
- export declare function Markdown({ source }: {
6
+ export declare function Markdown({ source, baseUrl }: {
7
7
  source: string;
8
+ baseUrl?: string;
8
9
  }): React.ReactElement;
@@ -0,0 +1,33 @@
1
+ import React from "react";
2
+ import type { VideoInfo } from "../core/index.js";
3
+ export type ImageInput = string | Blob | ArrayBuffer | Uint8Array | null | undefined;
4
+ export interface ResolvedImage {
5
+ src: string;
6
+ /** Call when the image is no longer used (revokes object URLs). */
7
+ release?: () => void;
8
+ }
9
+ /**
10
+ * Turns any supported image source into something an <img> can show:
11
+ * https/http URLs, `data:image/*;base64,…`, raw base64 text, `blob:` URLs, Blob/File/ArrayBuffer objects, `file://` URLs,
12
+ * absolute local paths (C:\… or /…), and relative paths (resolved against `base`, e.g. a plugin's asset URL).
13
+ * Anything else (javascript:, other data: types…) returns undefined.
14
+ */
15
+ export declare function toImageSrc(input: ImageInput, opts?: {
16
+ base?: string;
17
+ }): ResolvedImage | undefined;
18
+ /** <img> that accepts any source accepted by `toImageSrc`, with a graceful placeholder when it fails to load. */
19
+ export declare function Img({ src, base, alt, fallback, className, style, ...rest }: {
20
+ src: ImageInput;
21
+ base?: string;
22
+ alt?: string;
23
+ fallback?: React.ReactNode;
24
+ } & Omit<React.ImgHTMLAttributes<HTMLImageElement>, "src" | "alt">): React.ReactElement;
25
+ /**
26
+ * Video player. Hosted players (YouTube, Vimeo, Dailymotion, Loom, Streamable) use a sandboxed iframe; direct media files
27
+ * (mp4/webm/ogg/mov, https or shipped inside a plugin) use the native HTML5 player; anything else becomes a link.
28
+ */
29
+ export declare function VideoPlayer({ video, poster, title }: {
30
+ video: VideoInfo;
31
+ poster?: string;
32
+ title?: string;
33
+ }): React.ReactElement;
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ /**
3
+ * macOS "System Settings"-style screen. Sections are contributed to the `settings` extension point by the host and by
4
+ * plugins (language, appearance, your own…). Register the route `/settings/:section` (and `/settings`) with this component.
5
+ */
6
+ export declare function SettingsPage(): React.ReactElement;