@termix-ssh/plugin-sdk 1.0.1 → 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 (40) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/LICENSE +202 -0
  3. package/README.md +4 -3
  4. package/cli/commands/build.mjs +37 -12
  5. package/cli/commands/dev.mjs +1 -1
  6. package/cli/commands/openapi.mjs +39 -0
  7. package/cli/commands/validate.mjs +67 -0
  8. package/cli/index.mjs +3 -0
  9. package/cli/lib/css-classes.mjs +37 -0
  10. package/dist/changelog.d.ts +3 -8
  11. package/dist/changelog.js +6 -14
  12. package/dist/docs.d.ts +28 -0
  13. package/dist/docs.js +39 -0
  14. package/dist/frontend.d.ts +17 -0
  15. package/dist/frontend.js +8 -0
  16. package/dist/host/{chunk-COGRSHKO.js → chunk-F5RZH6HE.js} +13 -2
  17. package/dist/host/{chunk-3KG44G55.js → chunk-O2OHNVFX.js} +1 -1
  18. package/dist/host/{chunk-7GL25GFS.js → chunk-RU4E5XLY.js} +531 -300
  19. package/dist/host/core-classes.json +1 -0
  20. package/dist/host/{linked-server-BD573BEG.js → linked-server-4VWYYABP.js} +1 -1
  21. package/dist/host/{rbac-api-7GHDJLU4.js → rbac-api-WIJVZH3M.js} +1 -1
  22. package/dist/host/testing-host.js +3 -3
  23. package/dist/host/types/src/ui/api/plugins-api.d.ts +23 -0
  24. package/dist/host/types/src/ui/api/system-status-api.d.ts +24 -0
  25. package/dist/host/types/src/ui/components/docs-link.d.ts +25 -0
  26. package/dist/host/types/src/ui/components/panel-layout.d.ts +3 -1
  27. package/dist/host/types/src/ui/hooks/use-installed-plugins.d.ts +6 -0
  28. package/dist/host/types/src/ui/lib/docs.d.ts +10 -0
  29. package/dist/host/types/src/ui/main-axios.d.ts +1 -0
  30. package/dist/host/types/src/ui/plugin-host/sdk-ui.d.ts +1 -0
  31. package/dist/host/types/src/ui/settings/settings-fields-util.d.ts +10 -0
  32. package/dist/host/ui.js +139 -110
  33. package/dist/host/{use-permissions-SKWXHWDJ.js → use-permissions-KIQCJ35M.js} +1 -1
  34. package/dist/manifest.d.ts +16 -1
  35. package/dist/manifest.js +84 -1
  36. package/dist/openapi.d.ts +43 -0
  37. package/dist/openapi.js +60 -0
  38. package/dist/testing/setup.js +1 -0
  39. package/package.json +12 -2
  40. package/schema/manifest.schema.json +27 -0
@@ -2,10 +2,11 @@ const __termixNoGlob = () => ({});
2
2
  import {
3
3
  hasPermission,
4
4
  usePermissions
5
- } from "./chunk-3KG44G55.js";
5
+ } from "./chunk-O2OHNVFX.js";
6
6
  import {
7
7
  PRESETS,
8
8
  SYNC_CHANGED_EVENT,
9
+ TRUST_TOKEN_KEY,
9
10
  authApi,
10
11
  buildOriginWsUrl,
11
12
  byOrderThenId,
@@ -38,7 +39,7 @@ import {
38
39
  usePluginStore,
39
40
  useTabTypes,
40
41
  websocketAuthProtocols
41
- } from "./chunk-COGRSHKO.js";
42
+ } from "./chunk-F5RZH6HE.js";
42
43
  import {
43
44
  __export,
44
45
  define_import_meta_env_default
@@ -14572,9 +14573,6 @@ function resetShellBridge() {
14572
14573
  setShellHosts([], false);
14573
14574
  }
14574
14575
 
14575
- // src/ui/components/panel-layout.tsx
14576
- import { Children as Children5, Fragment as Fragment11, useRef as useRef28 } from "react";
14577
-
14578
14576
  // node_modules/lucide-react/dist/esm/icons/index.mjs
14579
14577
  var icons_exports = {};
14580
14578
  __export(icons_exports, {
@@ -48949,9 +48947,294 @@ var __iconData1857 = {
48949
48947
  __iconData1857.node;
48950
48948
  var ZoomOut = createLucideIcon(__iconData1857);
48951
48949
 
48952
- // src/ui/components/panel-layout.tsx
48950
+ // src/ui/components/docs-link.tsx
48951
+ import { useTranslation as useTranslation2 } from "react-i18next";
48952
+
48953
+ // packages/plugin-sdk/dist/docs.js
48954
+ function pluginDocsUrl(value) {
48955
+ if (typeof value !== "string")
48956
+ return null;
48957
+ try {
48958
+ const url = new URL(value.trim());
48959
+ if (url.protocol !== "https:")
48960
+ return null;
48961
+ return url.toString().replace(/\/$/, "");
48962
+ } catch {
48963
+ return null;
48964
+ }
48965
+ }
48966
+ function pluginDocsPage(base, page, anchor) {
48967
+ const clean = (page ?? "").replace(/^\/+|\/+$/g, "");
48968
+ const url = clean ? `${base}/${clean}` : base;
48969
+ return anchor ? `${url}#${anchor.replace(/^#/, "")}` : url;
48970
+ }
48971
+
48972
+ // src/ui/lib/docs-pages.json
48973
+ var docs_pages_default = {
48974
+ home: "",
48975
+ install: "install",
48976
+ updating: "install/updating",
48977
+ betas: "configure/betas",
48978
+ envVars: "configure/environment-variables",
48979
+ https: "configure/https",
48980
+ database: "configure/database",
48981
+ reverseProxy: "configure/reverse-proxy",
48982
+ desktopSync: "configure/desktop-sync",
48983
+ interface: "guide/interface",
48984
+ dashboard: "guide/dashboard",
48985
+ manage: "guide/manage",
48986
+ hosts: "guide/hosts",
48987
+ portKnocking: "guide/hosts#port-knocking",
48988
+ hostDefaults: "guide/host-defaults",
48989
+ credentials: "guide/credentials",
48990
+ sharing: "guide/sharing",
48991
+ account: "guide/account",
48992
+ accountSecurity: "guide/account#security",
48993
+ accountData: "guide/account#your-data",
48994
+ apiKeys: "guide/api-keys",
48995
+ commandPalette: "guide/command-palette",
48996
+ settings: "guide/settings",
48997
+ interfaceSettings: "guide/settings#interface",
48998
+ appearance: "guide/settings#appearance",
48999
+ keybindings: "guide/settings#keybindings",
49000
+ users: "guide/users",
49001
+ sessions: "guide/users#sessions",
49002
+ roles: "guide/roles",
49003
+ adminGeneral: "guide/admin-settings",
49004
+ branding: "guide/admin-settings#branding",
49005
+ adminDatabase: "guide/admin-settings#database",
49006
+ adminSsl: "configure/https",
49007
+ auditLog: "guide/audit-log",
49008
+ importExport: "guide/import-export",
49009
+ plugins: "guide/plugins",
49010
+ pluginCatalog: "plugins",
49011
+ cli: "cli",
49012
+ develop: "develop"
49013
+ };
49014
+
49015
+ // src/ui/lib/docs.ts
49016
+ var DOCS_URL = "https://docs.termix.site";
49017
+ function docsUrl(page = "home") {
49018
+ const path2 = docs_pages_default[page] ?? "";
49019
+ return path2 ? `${DOCS_URL}/${path2}` : `${DOCS_URL}/`;
49020
+ }
49021
+
49022
+ // src/ui/plugin-host/scope.tsx
49023
+ import {
49024
+ Component as Component2,
49025
+ createContext as createContext6,
49026
+ Suspense,
49027
+ useContext as useContext5
49028
+ } from "react";
49029
+
49030
+ // src/ui/plugin-host/host-scope.ts
49031
+ function isHostLike(value) {
49032
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
49033
+ if (!("id" in value)) return false;
49034
+ return "pluginSettings" in value || "ip" in value && "port" in value;
49035
+ }
49036
+ var cache = /* @__PURE__ */ new WeakMap();
49037
+ function scopeHost(value, pluginId) {
49038
+ if (!isHostLike(value)) return value;
49039
+ const full = shellHost(value.id)?.pluginSettings ?? value.pluginSettings;
49040
+ let perPlugin = cache.get(value);
49041
+ const hit = perPlugin?.get(pluginId);
49042
+ if (hit && hit.source === full) return hit.scoped;
49043
+ const own = full?.[pluginId];
49044
+ const scoped = { ...value, pluginSettings: own ? { [pluginId]: own } : {} };
49045
+ if (!perPlugin) {
49046
+ perPlugin = /* @__PURE__ */ new Map();
49047
+ cache.set(value, perPlugin);
49048
+ }
49049
+ perPlugin.set(pluginId, { source: full, scoped });
49050
+ return scoped;
49051
+ }
49052
+ function scopeHostArgs(args, pluginId) {
49053
+ return args.map(
49054
+ (arg) => isHostLike(arg) ? scopeHost(arg, pluginId) : scopeHostFields(arg, pluginId)
49055
+ );
49056
+ }
49057
+ var HOST_FIELDS = ["host", "sshHost", "hostConfig"];
49058
+ function scopeHostFields(value, pluginId) {
49059
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value;
49060
+ const record = value;
49061
+ let next = null;
49062
+ for (const field of HOST_FIELDS) {
49063
+ if (!(field in record)) continue;
49064
+ const scoped = scopeHost(record[field], pluginId);
49065
+ if (scoped === record[field]) continue;
49066
+ next ??= { ...record };
49067
+ next[field] = scoped;
49068
+ }
49069
+ return next ?? value;
49070
+ }
49071
+ function scopeHostCallback(fn, pluginId) {
49072
+ if (!fn) return fn;
49073
+ return ((...args) => fn(...scopeHostArgs(args, pluginId)));
49074
+ }
49075
+
49076
+ // src/ui/plugin-host/scope.tsx
48953
49077
  import { useTranslation } from "react-i18next";
48954
- import { Fragment as Fragment12, jsx as jsx29, jsxs as jsxs7 } from "react/jsx-runtime";
49078
+ import { jsx as jsx29, jsxs as jsxs7 } from "react/jsx-runtime";
49079
+ var PluginScopeContext = createContext6(null);
49080
+ function usePluginScope() {
49081
+ return useContext5(PluginScopeContext);
49082
+ }
49083
+ function CrashNotice({
49084
+ pluginId,
49085
+ onRetry
49086
+ }) {
49087
+ const { t } = useTranslation();
49088
+ const name = getPluginRecord(pluginId)?.summary.name ?? pluginId;
49089
+ return /* @__PURE__ */ jsxs7("div", { className: "flex flex-col items-center justify-center gap-2 p-4 text-center text-sm text-muted-foreground", children: [
49090
+ /* @__PURE__ */ jsx29("span", { children: t("plugins.runtime.crashed", { name }) }),
49091
+ /* @__PURE__ */ jsx29(
49092
+ "button",
49093
+ {
49094
+ type: "button",
49095
+ className: "text-xs underline underline-offset-2 hover:text-foreground",
49096
+ onClick: onRetry,
49097
+ children: t("plugins.runtime.retry")
49098
+ }
49099
+ )
49100
+ ] });
49101
+ }
49102
+ var PluginErrorBoundary = class extends Component2 {
49103
+ state = { error: false };
49104
+ static getDerivedStateFromError() {
49105
+ return { error: true };
49106
+ }
49107
+ componentDidCatch(error, info) {
49108
+ console.error(
49109
+ `[plugins] ${this.props.pluginId} crashed while rendering`,
49110
+ error,
49111
+ info.componentStack
49112
+ );
49113
+ }
49114
+ render() {
49115
+ if (this.state.error) {
49116
+ return /* @__PURE__ */ jsx29(
49117
+ CrashNotice,
49118
+ {
49119
+ pluginId: this.props.pluginId,
49120
+ onRetry: () => this.setState({ error: false })
49121
+ }
49122
+ );
49123
+ }
49124
+ return this.props.children;
49125
+ }
49126
+ };
49127
+ function PluginScope({
49128
+ pluginId,
49129
+ children
49130
+ }) {
49131
+ return /* @__PURE__ */ jsx29(PluginScopeContext.Provider, { value: pluginId, children: /* @__PURE__ */ jsx29(PluginErrorBoundary, { pluginId, children: /* @__PURE__ */ jsx29(Suspense, { fallback: null, children }) }) });
49132
+ }
49133
+ function withPluginScope(pluginId, Inner) {
49134
+ function Scoped(props) {
49135
+ return /* @__PURE__ */ jsx29(PluginScope, { pluginId, children: /* @__PURE__ */ jsx29(Inner, { ...scopeHostFields(props, pluginId) }) });
49136
+ }
49137
+ Scoped.displayName = `Plugin(${pluginId}:${Inner.displayName ?? Inner.name ?? "Component"})`;
49138
+ return Scoped;
49139
+ }
49140
+ var IconBoundary = class extends Component2 {
49141
+ state = { error: false };
49142
+ static getDerivedStateFromError() {
49143
+ return { error: true };
49144
+ }
49145
+ componentDidCatch(error) {
49146
+ console.error(`[plugins] ${this.props.pluginId} icon failed`, error);
49147
+ }
49148
+ render() {
49149
+ return this.state.error ? null : this.props.children;
49150
+ }
49151
+ };
49152
+ function withIconBoundary(pluginId, Icon2) {
49153
+ if (!Icon2) return void 0;
49154
+ function SafeIcon(props) {
49155
+ return /* @__PURE__ */ jsx29(IconBoundary, { pluginId, children: /* @__PURE__ */ jsx29(Icon2, { ...props }) });
49156
+ }
49157
+ SafeIcon.displayName = `PluginIcon(${pluginId})`;
49158
+ return SafeIcon;
49159
+ }
49160
+ function guardCallback(pluginId, fn, fallback) {
49161
+ if (!fn) return void 0;
49162
+ return (...args) => {
49163
+ try {
49164
+ return fn(...args);
49165
+ } catch (error) {
49166
+ console.error(`[plugins] ${pluginId} callback failed`, error);
49167
+ return fallback;
49168
+ }
49169
+ };
49170
+ }
49171
+
49172
+ // src/ui/components/docs-link.tsx
49173
+ import { jsx as jsx30 } from "react/jsx-runtime";
49174
+ function usePluginDocsUrl(pluginId, page, anchor) {
49175
+ const { records } = usePluginStore();
49176
+ const base = pluginId ? records.get(pluginId)?.summary.docs : void 0;
49177
+ return base ? pluginDocsPage(base, page, anchor) : null;
49178
+ }
49179
+ function DocsLink({
49180
+ core,
49181
+ page,
49182
+ anchor,
49183
+ href,
49184
+ variant = "text",
49185
+ children,
49186
+ className
49187
+ }) {
49188
+ const { t } = useTranslation2();
49189
+ const pluginId = usePluginScope();
49190
+ const pluginUrl = usePluginDocsUrl(
49191
+ page !== void 0 ? pluginId : null,
49192
+ page,
49193
+ anchor
49194
+ );
49195
+ const url = href ?? (core ? withAnchor(docsUrl(core), anchor) : null) ?? pluginUrl;
49196
+ if (!url) return null;
49197
+ const label = t("common.docs");
49198
+ if (variant === "icon") {
49199
+ return /* @__PURE__ */ jsx30(
49200
+ "a",
49201
+ {
49202
+ href: url,
49203
+ target: "_blank",
49204
+ rel: "noopener noreferrer",
49205
+ title: label,
49206
+ "aria-label": label,
49207
+ className: cn(
49208
+ "flex size-7 shrink-0 items-center justify-center text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground",
49209
+ className
49210
+ ),
49211
+ children: /* @__PURE__ */ jsx30(BookOpen, { className: "size-3.5" })
49212
+ }
49213
+ );
49214
+ }
49215
+ return /* @__PURE__ */ jsx30(
49216
+ "a",
49217
+ {
49218
+ href: url,
49219
+ target: "_blank",
49220
+ rel: "noopener noreferrer",
49221
+ className: cn(
49222
+ "shrink-0 text-[10px] text-accent-brand hover:underline",
49223
+ className
49224
+ ),
49225
+ children: children ?? label
49226
+ }
49227
+ );
49228
+ }
49229
+ function withAnchor(url, anchor) {
49230
+ if (!anchor) return url;
49231
+ return `${url.split("#")[0]}#${anchor.replace(/^#/, "")}`;
49232
+ }
49233
+
49234
+ // src/ui/components/panel-layout.tsx
49235
+ import { Children as Children5, Fragment as Fragment11, useRef as useRef28 } from "react";
49236
+ import { useTranslation as useTranslation3 } from "react-i18next";
49237
+ import { Fragment as Fragment12, jsx as jsx31, jsxs as jsxs8 } from "react/jsx-runtime";
48955
49238
  var PANEL = {
48956
49239
  body: "p-2.5",
48957
49240
  gap: "gap-2",
@@ -48970,10 +49253,11 @@ function PanelShell({
48970
49253
  tabs,
48971
49254
  footer,
48972
49255
  scroll = true,
49256
+ docs,
48973
49257
  className,
48974
49258
  children
48975
49259
  }) {
48976
- const body = /* @__PURE__ */ jsx29(
49260
+ const body = /* @__PURE__ */ jsx31(
48977
49261
  "div",
48978
49262
  {
48979
49263
  className: cn(
@@ -48984,43 +49268,49 @@ function PanelShell({
48984
49268
  children
48985
49269
  }
48986
49270
  );
48987
- const bands = /* @__PURE__ */ jsxs7(Fragment12, { children: [
48988
- toolbar && /* @__PURE__ */ jsx29(
49271
+ const bands = /* @__PURE__ */ jsxs8(Fragment12, { children: [
49272
+ toolbar && /* @__PURE__ */ jsx31(
48989
49273
  "div",
48990
49274
  {
48991
49275
  className: `flex shrink-0 items-center gap-2 border-b border-border ${PANEL.band}`,
48992
49276
  children: toolbar
48993
49277
  }
48994
49278
  ),
48995
- tabs && /* @__PURE__ */ jsx29("div", { className: "shrink-0 border-b border-border px-1", children: tabs })
49279
+ tabs && /* @__PURE__ */ jsx31("div", { className: "shrink-0 border-b border-border px-1", children: tabs })
48996
49280
  ] });
48997
- const foot = footer && /* @__PURE__ */ jsx29("div", { className: "flex shrink-0 items-center gap-2 border-t border-border px-3 py-2", children: footer });
49281
+ const foot = footer && /* @__PURE__ */ jsx31("div", { className: "flex shrink-0 items-center gap-2 border-t border-border px-3 py-2", children: footer });
48998
49282
  if (!chrome) {
48999
- return /* @__PURE__ */ jsxs7("div", { className: "flex min-h-0 flex-1 flex-col", children: [
49283
+ return /* @__PURE__ */ jsxs8("div", { className: "flex min-h-0 flex-1 flex-col", children: [
49000
49284
  bands,
49001
49285
  body,
49002
49286
  foot
49003
49287
  ] });
49004
49288
  }
49005
- return /* @__PURE__ */ jsxs7("div", { className: "flex h-full min-h-0 flex-1 flex-col overflow-hidden bg-background", children: [
49006
- /* @__PURE__ */ jsxs7(
49289
+ return /* @__PURE__ */ jsxs8("div", { className: "flex h-full min-h-0 flex-1 flex-col overflow-hidden bg-background", children: [
49290
+ /* @__PURE__ */ jsxs8(
49007
49291
  "header",
49008
49292
  {
49009
49293
  className: `flex ${PANEL.header} shrink-0 flex-row items-center border-b border-border`,
49010
49294
  children: [
49011
- leading && /* @__PURE__ */ jsxs7(Fragment12, { children: [
49012
- leading,
49013
- /* @__PURE__ */ jsx29(Separator4, { orientation: "vertical" })
49014
- ] }),
49015
- /* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 flex-1 items-center gap-2 px-3", children: [
49016
- icon && /* @__PURE__ */ jsx29("span", { className: "shrink-0 text-accent-brand", children: icon }),
49017
- /* @__PURE__ */ jsx29("span", { className: "truncate text-base font-bold tracking-tight", children: title }),
49018
- status && /* @__PURE__ */ jsxs7(Fragment12, { children: [
49019
- /* @__PURE__ */ jsx29(Separator4, { orientation: "vertical", className: "h-4 self-center" }),
49020
- /* @__PURE__ */ jsx29("span", { className: "truncate text-[10px] font-semibold uppercase tracking-widest text-muted-foreground", children: status })
49295
+ leading && /* @__PURE__ */ jsx31("div", { className: "flex h-full shrink-0 border-r border-border", children: leading }),
49296
+ /* @__PURE__ */ jsxs8("div", { className: "flex min-w-0 flex-1 items-center gap-2 px-3", children: [
49297
+ icon && /* @__PURE__ */ jsx31("span", { className: "shrink-0 text-accent-brand", children: icon }),
49298
+ /* @__PURE__ */ jsx31("span", { className: "truncate text-base font-bold tracking-tight", children: title }),
49299
+ status && /* @__PURE__ */ jsxs8(Fragment12, { children: [
49300
+ /* @__PURE__ */ jsx31(
49301
+ Separator4,
49302
+ {
49303
+ orientation: "vertical",
49304
+ className: "h-4 data-[orientation=vertical]:self-center"
49305
+ }
49306
+ ),
49307
+ /* @__PURE__ */ jsx31("span", { className: "truncate text-[10px] font-semibold uppercase tracking-widest text-muted-foreground", children: status })
49021
49308
  ] })
49022
49309
  ] }),
49023
- actions4 && /* @__PURE__ */ jsx29("div", { className: "flex shrink-0 items-center gap-1 px-2", children: actions4 })
49310
+ (actions4 || docs) && /* @__PURE__ */ jsxs8("div", { className: "flex shrink-0 items-center gap-1 px-2", children: [
49311
+ actions4,
49312
+ docs && /* @__PURE__ */ jsx31(DocsLink, { href: docs, variant: "icon" })
49313
+ ] })
49024
49314
  ]
49025
49315
  }
49026
49316
  ),
@@ -49034,9 +49324,9 @@ function BackButton({
49034
49324
  label,
49035
49325
  className
49036
49326
  }) {
49037
- const { t } = useTranslation();
49327
+ const { t } = useTranslation3();
49038
49328
  const text = label ?? t("common.back");
49039
- return /* @__PURE__ */ jsx29(
49329
+ return /* @__PURE__ */ jsx31(
49040
49330
  "button",
49041
49331
  {
49042
49332
  type: "button",
@@ -49047,7 +49337,7 @@ function BackButton({
49047
49337
  "flex h-full w-12.5 shrink-0 items-center justify-center text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring",
49048
49338
  className
49049
49339
  ),
49050
- children: /* @__PURE__ */ jsx29(ArrowLeft, { className: "size-4" })
49340
+ children: /* @__PURE__ */ jsx31(ArrowLeft, { className: "size-4" })
49051
49341
  }
49052
49342
  );
49053
49343
  }
@@ -49063,10 +49353,10 @@ function PanelSearch({
49063
49353
  inputRef,
49064
49354
  className
49065
49355
  }) {
49066
- const { t } = useTranslation();
49356
+ const { t } = useTranslation3();
49067
49357
  const localRef = useRef28(null);
49068
49358
  const text = placeholder ?? t("common.search");
49069
- return /* @__PURE__ */ jsxs7(
49359
+ return /* @__PURE__ */ jsxs8(
49070
49360
  "div",
49071
49361
  {
49072
49362
  className: cn(
@@ -49075,8 +49365,8 @@ function PanelSearch({
49075
49365
  className
49076
49366
  ),
49077
49367
  children: [
49078
- /* @__PURE__ */ jsx29(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" }),
49079
- /* @__PURE__ */ jsx29(
49368
+ /* @__PURE__ */ jsx31(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" }),
49369
+ /* @__PURE__ */ jsx31(
49080
49370
  "input",
49081
49371
  {
49082
49372
  ref: (el) => {
@@ -49106,7 +49396,7 @@ function PanelSearch({
49106
49396
  className: "h-8 w-full min-w-0 border border-input bg-transparent pl-8 pr-7 text-xs outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30"
49107
49397
  }
49108
49398
  ),
49109
- value && /* @__PURE__ */ jsx29(
49399
+ value && /* @__PURE__ */ jsx31(
49110
49400
  "button",
49111
49401
  {
49112
49402
  type: "button",
@@ -49117,7 +49407,7 @@ function PanelSearch({
49117
49407
  localRef.current?.focus();
49118
49408
  },
49119
49409
  className: "absolute right-1.5 top-1/2 flex size-5 -translate-y-1/2 items-center justify-center text-muted-foreground hover:text-foreground",
49120
- children: /* @__PURE__ */ jsx29(X, { className: "size-3" })
49410
+ children: /* @__PURE__ */ jsx31(X, { className: "size-3" })
49121
49411
  }
49122
49412
  )
49123
49413
  ]
@@ -49130,14 +49420,14 @@ function Segmented({
49130
49420
  options: options2,
49131
49421
  className
49132
49422
  }) {
49133
- return /* @__PURE__ */ jsx29(
49423
+ return /* @__PURE__ */ jsx31(
49134
49424
  "div",
49135
49425
  {
49136
49426
  role: "radiogroup",
49137
49427
  className: cn("flex items-center border border-border", className),
49138
49428
  children: options2.map((option, i) => {
49139
49429
  const active = option.value === value;
49140
- return /* @__PURE__ */ jsxs7(
49430
+ return /* @__PURE__ */ jsxs8(
49141
49431
  "button",
49142
49432
  {
49143
49433
  type: "button",
@@ -49153,7 +49443,7 @@ function Segmented({
49153
49443
  children: [
49154
49444
  option.icon,
49155
49445
  option.label,
49156
- option.count !== void 0 && /* @__PURE__ */ jsx29("span", { className: "text-[10px] tabular-nums opacity-60", children: option.count })
49446
+ option.count !== void 0 && /* @__PURE__ */ jsx31("span", { className: "text-[10px] tabular-nums opacity-60", children: option.count })
49157
49447
  ]
49158
49448
  },
49159
49449
  option.value
@@ -49168,35 +49458,35 @@ function ViewToggle({
49168
49458
  density,
49169
49459
  onDensity
49170
49460
  }) {
49171
- const { t } = useTranslation();
49172
- return /* @__PURE__ */ jsxs7("div", { className: "flex items-center border border-border", children: [
49173
- /* @__PURE__ */ jsx29(
49461
+ const { t } = useTranslation3();
49462
+ return /* @__PURE__ */ jsxs8("div", { className: "flex items-center border border-border", children: [
49463
+ /* @__PURE__ */ jsx31(
49174
49464
  ToggleButton,
49175
49465
  {
49176
49466
  active: view === "grid",
49177
49467
  onClick: () => onView("grid"),
49178
49468
  title: t("panel.gridView"),
49179
- children: /* @__PURE__ */ jsx29(Grid3x3, { className: "size-4" })
49469
+ children: /* @__PURE__ */ jsx31(Grid3x3, { className: "size-4" })
49180
49470
  }
49181
49471
  ),
49182
- /* @__PURE__ */ jsx29(
49472
+ /* @__PURE__ */ jsx31(
49183
49473
  ToggleButton,
49184
49474
  {
49185
49475
  active: view === "list",
49186
49476
  onClick: () => onView("list"),
49187
49477
  title: t("panel.listView"),
49188
49478
  bordered: true,
49189
- children: /* @__PURE__ */ jsx29(List2, { className: "size-4" })
49479
+ children: /* @__PURE__ */ jsx31(List2, { className: "size-4" })
49190
49480
  }
49191
49481
  ),
49192
- density && onDensity && /* @__PURE__ */ jsx29(
49482
+ density && onDensity && /* @__PURE__ */ jsx31(
49193
49483
  ToggleButton,
49194
49484
  {
49195
49485
  active: density === "compact",
49196
49486
  onClick: () => onDensity(density === "compact" ? "comfortable" : "compact"),
49197
49487
  title: density === "compact" ? t("panel.comfortableRows") : t("panel.compactRows"),
49198
49488
  bordered: true,
49199
- children: /* @__PURE__ */ jsx29(Rows3, { className: "size-4" })
49489
+ children: /* @__PURE__ */ jsx31(Rows3, { className: "size-4" })
49200
49490
  }
49201
49491
  )
49202
49492
  ] });
@@ -49208,7 +49498,7 @@ function ToggleButton({
49208
49498
  bordered,
49209
49499
  children
49210
49500
  }) {
49211
- return /* @__PURE__ */ jsx29(
49501
+ return /* @__PURE__ */ jsx31(
49212
49502
  "button",
49213
49503
  {
49214
49504
  type: "button",
@@ -49231,9 +49521,9 @@ function Facts({
49231
49521
  }) {
49232
49522
  const items = Children5.toArray(children).filter(Boolean);
49233
49523
  if (items.length === 0) return null;
49234
- return /* @__PURE__ */ jsx29("span", { className: cn("flex min-w-0 items-center gap-2", className), children: items.map((child, i) => /* @__PURE__ */ jsxs7(Fragment11, { children: [
49235
- i > 0 && /* @__PURE__ */ jsx29("span", { "aria-hidden": true, className: "h-3 w-px shrink-0 bg-border" }),
49236
- /* @__PURE__ */ jsx29("span", { className: "min-w-0 truncate", children: child })
49524
+ return /* @__PURE__ */ jsx31("span", { className: cn("flex min-w-0 items-center gap-2", className), children: items.map((child, i) => /* @__PURE__ */ jsxs8(Fragment11, { children: [
49525
+ i > 0 && /* @__PURE__ */ jsx31("span", { "aria-hidden": true, className: "h-3 w-px shrink-0 bg-border" }),
49526
+ /* @__PURE__ */ jsx31("span", { className: "min-w-0 truncate", children: child })
49237
49527
  ] }, i)) });
49238
49528
  }
49239
49529
  function GroupHeading({
@@ -49242,10 +49532,10 @@ function GroupHeading({
49242
49532
  action,
49243
49533
  className
49244
49534
  }) {
49245
- return /* @__PURE__ */ jsxs7("div", { className: cn("flex items-center gap-2", className), children: [
49246
- /* @__PURE__ */ jsx29("span", { className: "text-[10px] font-semibold uppercase tracking-widest text-muted-foreground", children: title }),
49247
- count3 !== void 0 && /* @__PURE__ */ jsx29("span", { className: "text-[10px] tabular-nums text-muted-foreground/60", children: count3 }),
49248
- /* @__PURE__ */ jsx29("div", { className: "h-px flex-1 bg-border" }),
49535
+ return /* @__PURE__ */ jsxs8("div", { className: cn("flex items-center gap-2", className), children: [
49536
+ /* @__PURE__ */ jsx31("span", { className: "text-[10px] font-semibold uppercase tracking-widest text-muted-foreground", children: title }),
49537
+ count3 !== void 0 && /* @__PURE__ */ jsx31("span", { className: "text-[10px] tabular-nums text-muted-foreground/60", children: count3 }),
49538
+ /* @__PURE__ */ jsx31("div", { className: "h-px flex-1 bg-border" }),
49249
49539
  action
49250
49540
  ] });
49251
49541
  }
@@ -49257,9 +49547,9 @@ var PROMPT_DESTRUCTIVE_BUTTON = "inline-flex h-7 items-center justify-center gap
49257
49547
 
49258
49548
  // src/ui/components/surface/surface-scope.tsx
49259
49549
  import {
49260
- createContext as createContext6,
49550
+ createContext as createContext7,
49261
49551
  useCallback as useCallback18,
49262
- useContext as useContext5,
49552
+ useContext as useContext6,
49263
49553
  useEffect as useEffect27,
49264
49554
  useId as useId2,
49265
49555
  useLayoutEffect as useLayoutEffect5,
@@ -49268,9 +49558,9 @@ import {
49268
49558
  useState as useState25
49269
49559
  } from "react";
49270
49560
  import { createPortal as createPortal3 } from "react-dom";
49271
- import { useTranslation as useTranslation2 } from "react-i18next";
49272
- import { Fragment as Fragment13, jsx as jsx30, jsxs as jsxs8 } from "react/jsx-runtime";
49273
- var ScopeContext = createContext6(null);
49561
+ import { useTranslation as useTranslation4 } from "react-i18next";
49562
+ import { Fragment as Fragment13, jsx as jsx32, jsxs as jsxs9 } from "react/jsx-runtime";
49563
+ var ScopeContext = createContext7(null);
49274
49564
  function widest(widths) {
49275
49565
  if (widths.includes("wide")) return "wide";
49276
49566
  return widths.some(Boolean);
@@ -49339,7 +49629,7 @@ function SurfaceScope({
49339
49629
  [kind, overlay, openView, closeView, views, confirm, close]
49340
49630
  );
49341
49631
  const current = confirms[0];
49342
- return /* @__PURE__ */ jsx30(ScopeContext.Provider, { value, children: /* @__PURE__ */ jsxs8(
49632
+ return /* @__PURE__ */ jsx32(ScopeContext.Provider, { value, children: /* @__PURE__ */ jsxs9(
49343
49633
  "div",
49344
49634
  {
49345
49635
  className: cn(
@@ -49348,7 +49638,7 @@ function SurfaceScope({
49348
49638
  ),
49349
49639
  children: [
49350
49640
  children,
49351
- /* @__PURE__ */ jsx30(
49641
+ /* @__PURE__ */ jsx32(
49352
49642
  "div",
49353
49643
  {
49354
49644
  ref: setOverlay,
@@ -49358,7 +49648,7 @@ function SurfaceScope({
49358
49648
  )
49359
49649
  }
49360
49650
  ),
49361
- current && /* @__PURE__ */ jsx30(
49651
+ current && /* @__PURE__ */ jsx32(
49362
49652
  ConfirmCard,
49363
49653
  {
49364
49654
  options: current,
@@ -49371,13 +49661,13 @@ function SurfaceScope({
49371
49661
  ) });
49372
49662
  }
49373
49663
  function useSurfaceKind() {
49374
- return useContext5(ScopeContext)?.kind ?? null;
49664
+ return useContext6(ScopeContext)?.kind ?? null;
49375
49665
  }
49376
49666
  function useSurfaceClose() {
49377
- return useContext5(ScopeContext)?.close ?? null;
49667
+ return useContext6(ScopeContext)?.close ?? null;
49378
49668
  }
49379
49669
  function useConfirm() {
49380
- const scope = useContext5(ScopeContext);
49670
+ const scope = useContext6(ScopeContext);
49381
49671
  return useCallback18(
49382
49672
  (options2) => {
49383
49673
  if (scope) return scope.confirm(options2);
@@ -49391,7 +49681,7 @@ function ConfirmCard({
49391
49681
  options: options2,
49392
49682
  onSettle
49393
49683
  }) {
49394
- const { t } = useTranslation2();
49684
+ const { t } = useTranslation4();
49395
49685
  const confirmRef = useRef29(null);
49396
49686
  const titleId = useId2();
49397
49687
  const destructive = options2.destructive ?? true;
@@ -49416,7 +49706,7 @@ function ConfirmCard({
49416
49706
  if (previous && document.contains(previous)) previous.focus();
49417
49707
  };
49418
49708
  }, []);
49419
- return /* @__PURE__ */ jsx30(
49709
+ return /* @__PURE__ */ jsx32(
49420
49710
  PanePrompt,
49421
49711
  {
49422
49712
  open: true,
@@ -49427,8 +49717,8 @@ function ConfirmCard({
49427
49717
  description: options2.description,
49428
49718
  tone: destructive ? "destructive" : "default",
49429
49719
  onCancel: () => onSettle(false),
49430
- actions: /* @__PURE__ */ jsxs8(Fragment13, { children: [
49431
- /* @__PURE__ */ jsx30(
49720
+ actions: /* @__PURE__ */ jsxs9(Fragment13, { children: [
49721
+ /* @__PURE__ */ jsx32(
49432
49722
  "button",
49433
49723
  {
49434
49724
  type: "button",
@@ -49437,7 +49727,7 @@ function ConfirmCard({
49437
49727
  children: options2.cancelLabel ?? t("common.cancel")
49438
49728
  }
49439
49729
  ),
49440
- /* @__PURE__ */ jsxs8(
49730
+ /* @__PURE__ */ jsxs9(
49441
49731
  "button",
49442
49732
  {
49443
49733
  ref: confirmRef,
@@ -49446,7 +49736,7 @@ function ConfirmCard({
49446
49736
  className: destructive ? PROMPT_DESTRUCTIVE_BUTTON : PROMPT_PRIMARY_BUTTON,
49447
49737
  children: [
49448
49738
  options2.confirmLabel ?? (destructive ? t("common.delete") : t("common.confirm")),
49449
- /* @__PURE__ */ jsx30("span", { "aria-hidden": true, className: "ml-1.5 opacity-60", children: "\u21B5" })
49739
+ /* @__PURE__ */ jsx32("span", { "aria-hidden": true, className: "ml-1.5 opacity-60", children: "\u21B5" })
49450
49740
  ]
49451
49741
  }
49452
49742
  )
@@ -49473,31 +49763,31 @@ function PanePrompt({
49473
49763
  const fallbackId = useId2();
49474
49764
  if (!open) return null;
49475
49765
  const headingId = titleId ?? fallbackId;
49476
- return /* @__PURE__ */ jsx30(
49766
+ return /* @__PURE__ */ jsx32(
49477
49767
  "div",
49478
49768
  {
49479
49769
  className: cn(
49480
- "motion-context-enter absolute inset-0 flex items-center justify-center overflow-y-auto p-4",
49481
- layer === "connection" ? "z-500 bg-canvas" : "z-50 bg-background/80 backdrop-blur-sm"
49770
+ "motion-fade-enter absolute inset-0 flex items-center justify-center overflow-y-auto p-4",
49771
+ layer === "connection" ? "z-500 bg-canvas" : "z-50 bg-background/85"
49482
49772
  ),
49483
49773
  style: layer === "connection" && backgroundColor ? { backgroundColor } : void 0,
49484
49774
  onMouseDown: (e) => {
49485
49775
  if (e.target === e.currentTarget) onCancel?.();
49486
49776
  },
49487
- children: /* @__PURE__ */ jsxs8(
49777
+ children: /* @__PURE__ */ jsxs9(
49488
49778
  "div",
49489
49779
  {
49490
49780
  role,
49491
49781
  "aria-modal": "true",
49492
49782
  "aria-labelledby": labelledBy ?? headingId,
49493
49783
  className: cn(
49494
- "my-auto flex w-full max-w-sm flex-col border bg-popover shadow-xl",
49784
+ "motion-context-enter my-auto flex w-full max-w-sm flex-col border bg-popover shadow-xl",
49495
49785
  tone === "destructive" ? "border-destructive/40" : "border-border",
49496
49786
  className
49497
49787
  ),
49498
49788
  children: [
49499
- /* @__PURE__ */ jsxs8("div", { className: "flex items-start gap-2.5 px-4 pt-4", children: [
49500
- icon && /* @__PURE__ */ jsx30(
49789
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-start gap-2.5 px-4 pt-4", children: [
49790
+ icon && /* @__PURE__ */ jsx32(
49501
49791
  "span",
49502
49792
  {
49503
49793
  className: cn(
@@ -49507,8 +49797,8 @@ function PanePrompt({
49507
49797
  children: icon
49508
49798
  }
49509
49799
  ),
49510
- /* @__PURE__ */ jsxs8("div", { className: "flex min-w-0 flex-col gap-1", children: [
49511
- /* @__PURE__ */ jsx30(
49800
+ /* @__PURE__ */ jsxs9("div", { className: "flex min-w-0 flex-col gap-1", children: [
49801
+ /* @__PURE__ */ jsx32(
49512
49802
  "p",
49513
49803
  {
49514
49804
  id: headingId,
@@ -49516,12 +49806,12 @@ function PanePrompt({
49516
49806
  children: title
49517
49807
  }
49518
49808
  ),
49519
- description && /* @__PURE__ */ jsx30("div", { className: "text-xs leading-snug text-muted-foreground", children: description })
49809
+ description && /* @__PURE__ */ jsx32("div", { className: "text-xs leading-snug text-muted-foreground", children: description })
49520
49810
  ] })
49521
49811
  ] }),
49522
- children && /* @__PURE__ */ jsx30("div", { className: "px-4 pt-3", children }),
49523
- actions4 && /* @__PURE__ */ jsx30("div", { className: "flex justify-end gap-2 px-4 py-3", children: actions4 }),
49524
- !actions4 && /* @__PURE__ */ jsx30("div", { className: "h-4" })
49812
+ children && /* @__PURE__ */ jsx32("div", { className: "px-4 pt-3", children }),
49813
+ actions4 && /* @__PURE__ */ jsx32("div", { className: "flex justify-end gap-2 px-4 py-3", children: actions4 }),
49814
+ !actions4 && /* @__PURE__ */ jsx32("div", { className: "h-4" })
49525
49815
  ]
49526
49816
  }
49527
49817
  )
@@ -49544,7 +49834,7 @@ function InlineView({
49544
49834
  className,
49545
49835
  children
49546
49836
  }) {
49547
- const scope = useContext5(ScopeContext);
49837
+ const scope = useContext6(ScopeContext);
49548
49838
  const id = useId2();
49549
49839
  const { openView, closeView } = scope ?? {};
49550
49840
  useLayoutEffect5(() => {
@@ -49558,7 +49848,7 @@ function InlineView({
49558
49848
  }, [onBeforeClose, onOpenChange]);
49559
49849
  if (!open) return null;
49560
49850
  const padded = scope?.kind === "tab" || scope?.kind === "pane";
49561
- const view = /* @__PURE__ */ jsx30(
49851
+ const view = /* @__PURE__ */ jsx32(
49562
49852
  "div",
49563
49853
  {
49564
49854
  className: cn(
@@ -49571,10 +49861,10 @@ function InlineView({
49571
49861
  void requestClose();
49572
49862
  }
49573
49863
  },
49574
- children: /* @__PURE__ */ jsx30(
49864
+ children: /* @__PURE__ */ jsx32(
49575
49865
  PanelShell,
49576
49866
  {
49577
- leading: /* @__PURE__ */ jsx30(BackButton, { onClick: () => void requestClose() }),
49867
+ leading: /* @__PURE__ */ jsx32(BackButton, { onClick: () => void requestClose() }),
49578
49868
  icon,
49579
49869
  title,
49580
49870
  status,
@@ -49582,7 +49872,7 @@ function InlineView({
49582
49872
  toolbar,
49583
49873
  footer,
49584
49874
  scroll,
49585
- children: bare ? children : /* @__PURE__ */ jsx30(
49875
+ children: bare ? children : /* @__PURE__ */ jsx32(
49586
49876
  "div",
49587
49877
  {
49588
49878
  className: cn(
@@ -49601,7 +49891,7 @@ function InlineView({
49601
49891
  if (scope?.overlay) return createPortal3(view, scope.overlay);
49602
49892
  if (scope) return null;
49603
49893
  return createPortal3(
49604
- /* @__PURE__ */ jsx30("div", { className: "fixed inset-0 z-50 bg-background", children: view }),
49894
+ /* @__PURE__ */ jsx32("div", { className: "fixed inset-0 z-50 bg-background", children: view }),
49605
49895
  document.body
49606
49896
  );
49607
49897
  }
@@ -50252,7 +50542,8 @@ var en_default = {
50252
50542
  mobileSidebarDescription: "Displays the mobile sidebar.",
50253
50543
  toggleSidebar: "Toggle Sidebar",
50254
50544
  search: "Search...",
50255
- new: "New"
50545
+ new: "New",
50546
+ docs: "Docs"
50256
50547
  },
50257
50548
  onboarding: {
50258
50549
  stepCounter: "Step {{current}} of {{total}}",
@@ -50296,7 +50587,8 @@ var en_default = {
50296
50587
  desktopLocalBody: "Everything is stored here. Nothing to set up.",
50297
50588
  desktopLinkTitle: "With my Termix server",
50298
50589
  desktopLinkBody: "Sign in to your server to use the same hosts and settings on every device.",
50299
- desktopLinked: "Linked to {{server}}"
50590
+ desktopLinked: "Linked to {{server}}",
50591
+ pluginsDocsLink: "Read about each one"
50300
50592
  },
50301
50593
  nav: {
50302
50594
  home: "Home",
@@ -50974,7 +51266,8 @@ var en_default = {
50974
51266
  select: "Select",
50975
51267
  toggleWith: "Toggle with",
50976
51268
  pluginActions: "More actions",
50977
- noTargetTab: "No terminal tab is open"
51269
+ noTargetTab: "No terminal tab is open",
51270
+ documentation: "Documentation"
50978
51271
  },
50979
51272
  splitScreen: {
50980
51273
  title: "Split screen",
@@ -51082,7 +51375,7 @@ var en_default = {
51082
51375
  empty: "No cards. Use Add below to place cards."
51083
51376
  },
51084
51377
  auth: {
51085
- tagline: "Self-hosted SSH and remote desktop management",
51378
+ tagline: "Self-hosted, plugin-based server management",
51086
51379
  loginTitle: "Welcome back",
51087
51380
  registerTitle: "Create Account",
51088
51381
  forgotPassword: "Forgot Password?",
@@ -51216,7 +51509,7 @@ var en_default = {
51216
51509
  title: "Dashboard",
51217
51510
  loading: "Loading dashboard...",
51218
51511
  github: "GitHub",
51219
- support: "Support",
51512
+ reportBug: "Report a bug",
51220
51513
  discord: "Discord",
51221
51514
  docs: "Docs",
51222
51515
  donate: "Donate",
@@ -51732,10 +52025,6 @@ var en_default = {
51732
52025
  twoFaOn: "On",
51733
52026
  twoFaOff: "Off",
51734
52027
  versionLabel: "Version",
51735
- betaProgramTitle: "Beta Program",
51736
- betaProgramDescription: "Try upcoming features early with the weekly rolling :beta Docker tag. Unstable, not for production.",
51737
- betaProgramFeedback: "Found a bug? Report it here.",
51738
- betaProgramLearnMore: "Learn More",
51739
52028
  cliTitle: "Termix CLI",
51740
52029
  cliDescription: "Open terminals, run commands and move files from your own shell.",
51741
52030
  cliLearnMore: "Learn More",
@@ -51870,7 +52159,13 @@ var en_default = {
51870
52159
  linkedProfileDescription: "This device uses your account on the server and keeps a copy of your data, so it still works offline.",
51871
52160
  manageSync: "Manage sync",
51872
52161
  setUpSync: "Link to a server",
51873
- settingsTabsAndShortcuts: "Tabs and Shortcuts"
52162
+ settingsTabsAndShortcuts: "Tabs and Shortcuts",
52163
+ onBetaTitle: "You are on a beta",
52164
+ onBetaDescription: "Thanks for testing. Tell us what breaks.",
52165
+ tryBetaTitle: "Try new features early",
52166
+ tryBetaDescription: "Join the beta program in Updates.",
52167
+ sendBetaFeedback: "Send beta feedback",
52168
+ openUpdates: "Updates"
51874
52169
  }
51875
52170
  }
51876
52171
  },
@@ -52152,8 +52447,9 @@ var en_default = {
52152
52447
  releaseNotes: "Release notes",
52153
52448
  noReleaseNotes: "No notes for this version.",
52154
52449
  viewRelease: "View on GitHub",
52155
- showAllReleases: "Show all {{count}}",
52156
- showFewerReleases: "Show fewer",
52450
+ showOlderReleases: "Older releases ({{count}})",
52451
+ hideOlderReleases: "Hide older releases",
52452
+ showMoreChanges: "Show {{count}} more",
52157
52453
  videoTitle: "{{name}} video",
52158
52454
  changeTypes: {
52159
52455
  added: "Added",
@@ -52165,6 +52461,7 @@ var en_default = {
52165
52461
  },
52166
52462
  installedBadge: "Installed",
52167
52463
  incompatible: "Needs a newer Termix",
52464
+ features: "Features",
52168
52465
  whatItCanDo: "What it can do",
52169
52466
  addsToTermix: "Adds to Termix",
52170
52467
  addsToTermixHint: "Uninstall and every one of these goes away.",
@@ -52209,7 +52506,27 @@ var en_default = {
52209
52506
  unverifiedHint: "Installed from a file. Termix did not check or sign it.",
52210
52507
  reportIssue: "Report an issue",
52211
52508
  reportTo: "Report to {{author}}",
52212
- openSettings: "Open its settings"
52509
+ requestFeature: "Request a feature",
52510
+ openSettings: "Open its settings",
52511
+ beta: {
52512
+ badge: "Beta",
52513
+ badgeHint: "This is a beta version. It can have bugs.",
52514
+ try: "Try beta {{version}}",
52515
+ confirmTitle: "Use betas for {{name}}?",
52516
+ confirmBody: "Betas get new features and fixes first, but they can have bugs. You can switch back to stable at any time.",
52517
+ confirmInstall: "Install {{version}}",
52518
+ confirmSwitch: "Use betas",
52519
+ leftWhileOnBeta: "{{name}} stays on {{version}} until a stable release is newer.",
52520
+ channel: "Beta versions",
52521
+ channelHint: "Get new versions early. They can have bugs.",
52522
+ waitingForStable: "Back on stable. Stays on {{version}} until a stable release is newer.",
52523
+ versionLabel: "{{version}} (beta)",
52524
+ sendFeedback: "Send beta feedback"
52525
+ },
52526
+ documentation: "Documentation",
52527
+ envVars: "Environment variables",
52528
+ envRequired: "Required",
52529
+ envDefault: "Default: {{value}}"
52213
52530
  }
52214
52531
  },
52215
52532
  settings: {
@@ -52248,7 +52565,39 @@ var en_default = {
52248
52565
  instanceSettings: "Server settings",
52249
52566
  nothingHere: "Nothing to set here.",
52250
52567
  searchPage: "Search this page",
52251
- noPageMatches: "Nothing on this page matches."
52568
+ noPageMatches: "Nothing on this page matches.",
52569
+ updates: {
52570
+ title: "Updates",
52571
+ betaProgram: "Beta program",
52572
+ betaIntro: "Betas are early builds of the next Termix version. They come out on Tuesdays and Fridays when there is something new to try. They can have bugs, and your feedback is what makes the stable release better.",
52573
+ termixBetas: "Termix betas",
52574
+ termixBetasHint: "Tell me about new betas, not only stable releases.",
52575
+ runningBetaBuild: "You are running a beta build. To leave, switch back to a stable image or installer.",
52576
+ howToInstall: "How to get the beta",
52577
+ desktopInstall: "Download the beta installer and install it over this one.",
52578
+ downloadBeta: "Download {{version}}",
52579
+ dockerInstall: "Change your image to the beta tag and restart the container:",
52580
+ copy: "Copy",
52581
+ copied: "Copied",
52582
+ copyFailed: "Could not copy",
52583
+ learnMore: "Learn more about betas",
52584
+ feedback: "Feedback",
52585
+ feedbackHint: "Found a bug or have an idea? It goes straight to the developers with your version filled in.",
52586
+ sendFeedback: "Send beta feedback",
52587
+ releases: "Releases",
52588
+ thisVersion: "This version",
52589
+ newestStable: "Newest stable",
52590
+ newestBeta: "Newest beta",
52591
+ noRelease: "None right now",
52592
+ pluginBetas: "Plugin betas",
52593
+ pluginBetasIntro: "Each plugin can use betas on its own. To try one, open it and press Try beta.",
52594
+ openPlugins: "Open Plugins",
52595
+ waitingForStable: "Back on stable. Stays on {{version}} until a stable release is newer.",
52596
+ backToStable: "Back to stable",
52597
+ allPluginsBeta: "Use betas for all plugins",
52598
+ allPluginsStable: "All plugins back to stable",
52599
+ saveFailed: "Could not save the change"
52600
+ }
52252
52601
  },
52253
52602
  managerCard: {
52254
52603
  refresh: "Refresh",
@@ -52462,6 +52811,7 @@ var en_default = {
52462
52811
  collapse: "Collapse",
52463
52812
  expand: "Expand",
52464
52813
  cannotBeUndone: "This cannot be undone.",
52814
+ hostNotFound: "Couldn't find that host. Try refreshing the list.",
52465
52815
  hostCount_one: "{{count}} host",
52466
52816
  hostCount_other: "{{count}} hosts",
52467
52817
  credentialCount_one: "{{count}} credential",
@@ -52593,7 +52943,9 @@ i18n.use(localeBackend).use(Browser).use(initReactI18next).init({
52593
52943
  escapeValue: false
52594
52944
  },
52595
52945
  react: {
52596
- useSuspense: false
52946
+ useSuspense: false,
52947
+ // Repaint when a plugin enabled later loads its strings.
52948
+ bindI18n: "languageChanged loaded"
52597
52949
  }
52598
52950
  });
52599
52951
  async function changeAppLanguage(language) {
@@ -52622,23 +52974,23 @@ import { useMemo as useMemo14, useSyncExternalStore as useSyncExternalStore3 } f
52622
52974
 
52623
52975
  // src/ui/contexts/UiPreferencesContext.tsx
52624
52976
  import {
52625
- createContext as createContext7,
52977
+ createContext as createContext8,
52626
52978
  useCallback as useCallback19,
52627
- useContext as useContext6,
52979
+ useContext as useContext7,
52628
52980
  useEffect as useEffect28,
52629
52981
  useMemo as useMemo13,
52630
52982
  useRef as useRef30,
52631
52983
  useState as useState26
52632
52984
  } from "react";
52633
- import { jsx as jsx31 } from "react/jsx-runtime";
52634
- var UiPreferencesContext = createContext7(
52985
+ import { jsx as jsx33 } from "react/jsx-runtime";
52986
+ var UiPreferencesContext = createContext8(
52635
52987
  null
52636
52988
  );
52637
52989
  function useUiPreferencesContext() {
52638
- return useContext6(UiPreferencesContext);
52990
+ return useContext7(UiPreferencesContext);
52639
52991
  }
52640
52992
  function useAreaPreferences(area) {
52641
- const ctx = useContext6(UiPreferencesContext);
52993
+ const ctx = useContext7(UiPreferencesContext);
52642
52994
  if (!ctx) return PRESETS.balanced[area];
52643
52995
  return ctx.resolve(area);
52644
52996
  }
@@ -52659,8 +53011,8 @@ registeredRailItems.subscribe(() => {
52659
53011
 
52660
53012
  // src/ui/components/tab-strip.tsx
52661
53013
  import { useEffect as useEffect29, useRef as useRef31, useState as useState27 } from "react";
52662
- import { useTranslation as useTranslation3 } from "react-i18next";
52663
- import { jsx as jsx32, jsxs as jsxs9 } from "react/jsx-runtime";
53014
+ import { useTranslation as useTranslation5 } from "react-i18next";
53015
+ import { jsx as jsx34, jsxs as jsxs10 } from "react/jsx-runtime";
52664
53016
  function TabStrip({
52665
53017
  tabs,
52666
53018
  activeTab,
@@ -52671,7 +53023,7 @@ function TabStrip({
52671
53023
  className
52672
53024
  }) {
52673
53025
  const ref = useRef31(null);
52674
- const { t } = useTranslation3();
53026
+ const { t } = useTranslation5();
52675
53027
  const [scroll, setScroll] = useState27({ left: false, right: false });
52676
53028
  useEffect29(() => {
52677
53029
  const el = ref.current;
@@ -52703,7 +53055,7 @@ function TabStrip({
52703
53055
  }, []);
52704
53056
  const renderTab = (tab) => {
52705
53057
  const active = isActive ? isActive(tab.id) : activeTab === tab.id;
52706
- return /* @__PURE__ */ jsxs9(
53058
+ return /* @__PURE__ */ jsxs10(
52707
53059
  "button",
52708
53060
  {
52709
53061
  type: "button",
@@ -52712,14 +53064,14 @@ function TabStrip({
52712
53064
  children: [
52713
53065
  tab.icon,
52714
53066
  tab.label,
52715
- tab.count !== void 0 && /* @__PURE__ */ jsx32("span", { className: "text-[10px] tabular-nums opacity-60", children: tab.count })
53067
+ tab.count !== void 0 && /* @__PURE__ */ jsx34("span", { className: "text-[10px] tabular-nums opacity-60", children: tab.count })
52716
53068
  ]
52717
53069
  },
52718
53070
  tab.id
52719
53071
  );
52720
53072
  };
52721
53073
  const overflow = scroll.left || scroll.right;
52722
- return /* @__PURE__ */ jsxs9(
53074
+ return /* @__PURE__ */ jsxs10(
52723
53075
  "div",
52724
53076
  {
52725
53077
  className: cn(
@@ -52728,7 +53080,7 @@ function TabStrip({
52728
53080
  className
52729
53081
  ),
52730
53082
  children: [
52731
- overflow && /* @__PURE__ */ jsx32(
53083
+ overflow && /* @__PURE__ */ jsx34(
52732
53084
  "button",
52733
53085
  {
52734
53086
  type: "button",
@@ -52740,11 +53092,11 @@ function TabStrip({
52740
53092
  left: -ref.current.clientWidth * 0.75,
52741
53093
  behavior: "smooth"
52742
53094
  }),
52743
- children: /* @__PURE__ */ jsx32(ChevronLeft, { className: "size-4" })
53095
+ children: /* @__PURE__ */ jsx34(ChevronLeft, { className: "size-4" })
52744
53096
  }
52745
53097
  ),
52746
- /* @__PURE__ */ jsx32("div", { ref, className: "min-w-0 flex-1 overflow-x-auto scrollbar-none", children: /* @__PURE__ */ jsx32("div", { className: "flex min-w-max", children: tabs.map(renderTab) }) }),
52747
- overflow && /* @__PURE__ */ jsx32(
53098
+ /* @__PURE__ */ jsx34("div", { ref, className: "min-w-0 flex-1 overflow-x-auto scrollbar-none", children: /* @__PURE__ */ jsx34("div", { className: "flex min-w-max", children: tabs.map(renderTab) }) }),
53099
+ overflow && /* @__PURE__ */ jsx34(
52748
53100
  "button",
52749
53101
  {
52750
53102
  type: "button",
@@ -52756,17 +53108,17 @@ function TabStrip({
52756
53108
  left: ref.current.clientWidth * 0.75,
52757
53109
  behavior: "smooth"
52758
53110
  }),
52759
- children: /* @__PURE__ */ jsx32(ChevronRight, { className: "size-4" })
53111
+ children: /* @__PURE__ */ jsx34(ChevronRight, { className: "size-4" })
52760
53112
  }
52761
53113
  ),
52762
- trailing && /* @__PURE__ */ jsx32("div", { className: "flex shrink-0 items-center gap-1 px-1", children: trailing })
53114
+ trailing && /* @__PURE__ */ jsx34("div", { className: "flex shrink-0 items-center gap-1 px-1", children: trailing })
52763
53115
  ]
52764
53116
  }
52765
53117
  );
52766
53118
  }
52767
53119
 
52768
53120
  // src/ui/sidebar/HostManagerTabs.tsx
52769
- import { jsx as jsx33 } from "react/jsx-runtime";
53121
+ import { jsx as jsx35 } from "react/jsx-runtime";
52770
53122
  var sections = createRegistry(byOrderThenId);
52771
53123
  var registerHostEditorSection = sections.register;
52772
53124
  var unregisterHostEditorSection = sections.unregister;
@@ -53072,7 +53424,7 @@ function createPluginApi(pluginId) {
53072
53424
  get(target, property, receiver) {
53073
53425
  const value = Reflect.get(target, property, receiver);
53074
53426
  if (typeof value !== "function") return value;
53075
- if (property === "get" || property === "delete" || property === "head" || property === "options" || property === "post" || property === "put" || property === "patch") {
53427
+ if (property === "get" || property === "delete" || property === "head" || property === "options" || property === "post" || property === "put" || property === "patch" || property === "postForm" || property === "putForm" || property === "patchForm") {
53076
53428
  return (url, ...rest) => value.call(
53077
53429
  target,
53078
53430
  `${prefix}${url.startsWith("/") ? url : `/${url}`}`,
@@ -53189,8 +53541,8 @@ function sectionPosition(section, order2 = 0) {
53189
53541
  }
53190
53542
 
53191
53543
  // src/ui/components/theme-provider.tsx
53192
- import { createContext as createContext8, useContext as useContext7, useEffect as useEffect30, useState as useState28 } from "react";
53193
- import { jsx as jsx34 } from "react/jsx-runtime";
53544
+ import { createContext as createContext9, useContext as useContext8, useEffect as useEffect30, useState as useState28 } from "react";
53545
+ import { jsx as jsx36 } from "react/jsx-runtime";
53194
53546
  var ALL_THEME_CLASSES = [
53195
53547
  "light",
53196
53548
  "dark",
@@ -53207,7 +53559,7 @@ var initialState = {
53207
53559
  setTheme: () => null,
53208
53560
  setThemePreview: () => null
53209
53561
  };
53210
- var ThemeProviderContext = createContext8(initialState);
53562
+ var ThemeProviderContext = createContext9(initialState);
53211
53563
  function ThemeProvider({
53212
53564
  children,
53213
53565
  defaultTheme = "dark",
@@ -53239,10 +53591,10 @@ function ThemeProvider({
53239
53591
  setPreviewTheme(preview);
53240
53592
  }
53241
53593
  };
53242
- return /* @__PURE__ */ jsx34(ThemeProviderContext.Provider, { ...props, value, children });
53594
+ return /* @__PURE__ */ jsx36(ThemeProviderContext.Provider, { ...props, value, children });
53243
53595
  }
53244
53596
  var useTheme = () => {
53245
- const context = useContext7(ThemeProviderContext);
53597
+ const context = useContext8(ThemeProviderContext);
53246
53598
  if (context === void 0)
53247
53599
  throw new Error("useTheme must be used within a ThemeProvider");
53248
53600
  return context;
@@ -53448,156 +53800,7 @@ async function getPlugins() {
53448
53800
  const response = await rbacApi.get("/plugins");
53449
53801
  return Array.isArray(response.data) ? response.data : [];
53450
53802
  }
53451
-
53452
- // src/ui/plugin-host/scope.tsx
53453
- import {
53454
- Component as Component2,
53455
- createContext as createContext9,
53456
- Suspense,
53457
- useContext as useContext8
53458
- } from "react";
53459
-
53460
- // src/ui/plugin-host/host-scope.ts
53461
- function isHostLike(value) {
53462
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
53463
- if (!("id" in value)) return false;
53464
- return "pluginSettings" in value || "ip" in value && "port" in value;
53465
- }
53466
- var cache = /* @__PURE__ */ new WeakMap();
53467
- function scopeHost(value, pluginId) {
53468
- if (!isHostLike(value)) return value;
53469
- const full = shellHost(value.id)?.pluginSettings ?? value.pluginSettings;
53470
- let perPlugin = cache.get(value);
53471
- const hit = perPlugin?.get(pluginId);
53472
- if (hit && hit.source === full) return hit.scoped;
53473
- const own = full?.[pluginId];
53474
- const scoped = { ...value, pluginSettings: own ? { [pluginId]: own } : {} };
53475
- if (!perPlugin) {
53476
- perPlugin = /* @__PURE__ */ new Map();
53477
- cache.set(value, perPlugin);
53478
- }
53479
- perPlugin.set(pluginId, { source: full, scoped });
53480
- return scoped;
53481
- }
53482
- function scopeHostArgs(args, pluginId) {
53483
- return args.map(
53484
- (arg) => isHostLike(arg) ? scopeHost(arg, pluginId) : scopeHostFields(arg, pluginId)
53485
- );
53486
- }
53487
- var HOST_FIELDS = ["host", "sshHost", "hostConfig"];
53488
- function scopeHostFields(value, pluginId) {
53489
- if (!value || typeof value !== "object" || Array.isArray(value)) return value;
53490
- const record = value;
53491
- let next = null;
53492
- for (const field of HOST_FIELDS) {
53493
- if (!(field in record)) continue;
53494
- const scoped = scopeHost(record[field], pluginId);
53495
- if (scoped === record[field]) continue;
53496
- next ??= { ...record };
53497
- next[field] = scoped;
53498
- }
53499
- return next ?? value;
53500
- }
53501
- function scopeHostCallback(fn, pluginId) {
53502
- if (!fn) return fn;
53503
- return ((...args) => fn(...scopeHostArgs(args, pluginId)));
53504
- }
53505
-
53506
- // src/ui/plugin-host/scope.tsx
53507
- import { useTranslation as useTranslation4 } from "react-i18next";
53508
- import { jsx as jsx35, jsxs as jsxs10 } from "react/jsx-runtime";
53509
- var PluginScopeContext = createContext9(null);
53510
- function usePluginScope() {
53511
- return useContext8(PluginScopeContext);
53512
- }
53513
- function CrashNotice({
53514
- pluginId,
53515
- onRetry
53516
- }) {
53517
- const { t } = useTranslation4();
53518
- const name = getPluginRecord(pluginId)?.summary.name ?? pluginId;
53519
- return /* @__PURE__ */ jsxs10("div", { className: "flex flex-col items-center justify-center gap-2 p-4 text-center text-sm text-muted-foreground", children: [
53520
- /* @__PURE__ */ jsx35("span", { children: t("plugins.runtime.crashed", { name }) }),
53521
- /* @__PURE__ */ jsx35(
53522
- "button",
53523
- {
53524
- type: "button",
53525
- className: "text-xs underline underline-offset-2 hover:text-foreground",
53526
- onClick: onRetry,
53527
- children: t("plugins.runtime.retry")
53528
- }
53529
- )
53530
- ] });
53531
- }
53532
- var PluginErrorBoundary = class extends Component2 {
53533
- state = { error: false };
53534
- static getDerivedStateFromError() {
53535
- return { error: true };
53536
- }
53537
- componentDidCatch(error, info) {
53538
- console.error(
53539
- `[plugins] ${this.props.pluginId} crashed while rendering`,
53540
- error,
53541
- info.componentStack
53542
- );
53543
- }
53544
- render() {
53545
- if (this.state.error) {
53546
- return /* @__PURE__ */ jsx35(
53547
- CrashNotice,
53548
- {
53549
- pluginId: this.props.pluginId,
53550
- onRetry: () => this.setState({ error: false })
53551
- }
53552
- );
53553
- }
53554
- return this.props.children;
53555
- }
53556
- };
53557
- function PluginScope({
53558
- pluginId,
53559
- children
53560
- }) {
53561
- return /* @__PURE__ */ jsx35(PluginScopeContext.Provider, { value: pluginId, children: /* @__PURE__ */ jsx35(PluginErrorBoundary, { pluginId, children: /* @__PURE__ */ jsx35(Suspense, { fallback: null, children }) }) });
53562
- }
53563
- function withPluginScope(pluginId, Inner) {
53564
- function Scoped(props) {
53565
- return /* @__PURE__ */ jsx35(PluginScope, { pluginId, children: /* @__PURE__ */ jsx35(Inner, { ...scopeHostFields(props, pluginId) }) });
53566
- }
53567
- Scoped.displayName = `Plugin(${pluginId}:${Inner.displayName ?? Inner.name ?? "Component"})`;
53568
- return Scoped;
53569
- }
53570
- var IconBoundary = class extends Component2 {
53571
- state = { error: false };
53572
- static getDerivedStateFromError() {
53573
- return { error: true };
53574
- }
53575
- componentDidCatch(error) {
53576
- console.error(`[plugins] ${this.props.pluginId} icon failed`, error);
53577
- }
53578
- render() {
53579
- return this.state.error ? null : this.props.children;
53580
- }
53581
- };
53582
- function withIconBoundary(pluginId, Icon2) {
53583
- if (!Icon2) return void 0;
53584
- function SafeIcon(props) {
53585
- return /* @__PURE__ */ jsx35(IconBoundary, { pluginId, children: /* @__PURE__ */ jsx35(Icon2, { ...props }) });
53586
- }
53587
- SafeIcon.displayName = `PluginIcon(${pluginId})`;
53588
- return SafeIcon;
53589
- }
53590
- function guardCallback(pluginId, fn, fallback) {
53591
- if (!fn) return void 0;
53592
- return (...args) => {
53593
- try {
53594
- return fn(...args);
53595
- } catch (error) {
53596
- console.error(`[plugins] ${pluginId} callback failed`, error);
53597
- return fallback;
53598
- }
53599
- };
53600
- }
53803
+ var PLUGINS_CHANGED_EVENT = "termix:plugins-changed";
53601
53804
 
53602
53805
  // src/ui/hooks/useSshAuthProviders.ts
53603
53806
  import { useEffect as useEffect32, useMemo as useMemo15, useState as useState30 } from "react";
@@ -53605,6 +53808,7 @@ import { useEffect as useEffect32, useMemo as useMemo15, useState as useState30
53605
53808
  // src/ui/api/auth-methods-api.ts
53606
53809
  function remember(data) {
53607
53810
  if (data?.token) localStorage.setItem("jwt", data.token);
53811
+ if (data?.trustToken) localStorage.setItem(TRUST_TOKEN_KEY, data.trustToken);
53608
53812
  if (data?.success && !data.requires_totp) markUserAuthenticated();
53609
53813
  return data;
53610
53814
  }
@@ -53758,7 +53962,7 @@ function contextKey(context) {
53758
53962
  try {
53759
53963
  return JSON.stringify(context);
53760
53964
  } catch {
53761
- return String(Object.keys(context));
53965
+ return Object.entries(context).map(([key, value]) => `${key}:${value == null ? "-" : typeof value}`).join(",");
53762
53966
  }
53763
53967
  }
53764
53968
 
@@ -53773,7 +53977,7 @@ import {
53773
53977
  useSyncExternalStore as useSyncExternalStore6,
53774
53978
  useState as useState31
53775
53979
  } from "react";
53776
- import { jsx as jsx36 } from "react/jsx-runtime";
53980
+ import { jsx as jsx37 } from "react/jsx-runtime";
53777
53981
  var StatusStoreContext = createContext10(null);
53778
53982
  var ServerStatusContext = createContext10(null);
53779
53983
  var activeStore = null;
@@ -53895,6 +54099,9 @@ var pluginHostBridge = {
53895
54099
  }
53896
54100
  return pluginId;
53897
54101
  },
54102
+ useDocsUrl(pluginId, page, anchor) {
54103
+ return usePluginDocsUrl(pluginId, page, anchor);
54104
+ },
53898
54105
  useTranslation(pluginId) {
53899
54106
  const { t, i18n: i18n2 } = useI18nTranslation(pluginId);
53900
54107
  return {
@@ -53912,13 +54119,22 @@ var pluginHostBridge = {
53912
54119
  useEffect34(() => {
53913
54120
  let cancelled = false;
53914
54121
  if (scope === "host" && !Number.isFinite(numericHostId)) return;
53915
- SETTINGS_READERS[scope](pluginId, numericHostId).then((values) => {
54122
+ const read = () => SETTINGS_READERS[scope](pluginId, numericHostId).then((values) => {
53916
54123
  if (!cancelled) setState({ values, loaded: true });
53917
54124
  }).catch(() => {
53918
54125
  if (!cancelled) setState((prev) => ({ ...prev, loaded: true }));
53919
54126
  });
54127
+ void read();
54128
+ const onChanged = (event) => {
54129
+ const detail = event.detail;
54130
+ if (detail?.pluginId !== pluginId || detail.scope !== scope) return;
54131
+ if (scope === "host" && detail.hostId !== numericHostId) return;
54132
+ void read();
54133
+ };
54134
+ window.addEventListener(PLUGIN_SETTINGS_CHANGED_EVENT, onChanged);
53920
54135
  return () => {
53921
54136
  cancelled = true;
54137
+ window.removeEventListener(PLUGIN_SETTINGS_CHANGED_EVENT, onChanged);
53922
54138
  };
53923
54139
  }, [pluginId, scope, numericHostId]);
53924
54140
  const save = useCallback21(
@@ -54112,6 +54328,18 @@ function pluginKey(pluginId, key) {
54112
54328
 
54113
54329
  // src/ui/plugin-host/app.ts
54114
54330
  var ONBOARDING_ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
54331
+ function createDocsApi(manifest) {
54332
+ const url = pluginDocsUrl(manifest?.docs);
54333
+ const page = (name, anchor) => url ? pluginDocsPage(url, name, anchor) : null;
54334
+ return {
54335
+ url,
54336
+ page,
54337
+ open(name, anchor) {
54338
+ const target = page(name, anchor);
54339
+ if (target) window.open(target, "_blank", "noopener,noreferrer");
54340
+ }
54341
+ };
54342
+ }
54115
54343
  function createPluginApp(pluginId, manifest, contributes, options2 = {}) {
54116
54344
  const bag = [];
54117
54345
  let disposed = false;
@@ -54516,6 +54744,7 @@ function createPluginApp(pluginId, manifest, contributes, options2 = {}) {
54516
54744
  onReady: (listener) => track(tabsApi.onReady(listener))
54517
54745
  },
54518
54746
  confirm: (options3) => shell.confirm(options3),
54747
+ docs: createDocsApi(manifest),
54519
54748
  desktop: {
54520
54749
  available: isElectron(),
54521
54750
  remoteServerUrl,
@@ -54558,8 +54787,8 @@ function createPluginApp(pluginId, manifest, contributes, options2 = {}) {
54558
54787
 
54559
54788
  // src/ui/shell/ActionSlot.tsx
54560
54789
  import React50 from "react";
54561
- import { useTranslation as useTranslation5 } from "react-i18next";
54562
- import { Fragment as Fragment14, jsx as jsx37, jsxs as jsxs11 } from "react/jsx-runtime";
54790
+ import { useTranslation as useTranslation6 } from "react-i18next";
54791
+ import { Fragment as Fragment14, jsx as jsx38, jsxs as jsxs11 } from "react/jsx-runtime";
54563
54792
  function ActionSlot({
54564
54793
  slotId,
54565
54794
  context,
@@ -54569,17 +54798,17 @@ function ActionSlot({
54569
54798
  hideLabels,
54570
54799
  when
54571
54800
  }) {
54572
- const { t } = useTranslation5();
54801
+ const { t } = useTranslation6();
54573
54802
  const contributions2 = useActionSlot(slotId, when).filter(
54574
54803
  (contribution) => contribution.kind !== "component"
54575
54804
  );
54576
54805
  if (!enabled || contributions2.length === 0) return null;
54577
- return /* @__PURE__ */ jsx37(Fragment14, { children: contributions2.map((contribution) => {
54806
+ return /* @__PURE__ */ jsx38(Fragment14, { children: contributions2.map((contribution) => {
54578
54807
  const invoke = () => {
54579
54808
  void invokeAction(contribution.actionId, ...context?.() ?? []);
54580
54809
  };
54581
54810
  if (renderItem) {
54582
- return /* @__PURE__ */ jsx37(React50.Fragment, { children: renderItem(contribution, invoke) }, contribution.actionId);
54811
+ return /* @__PURE__ */ jsx38(React50.Fragment, { children: renderItem(contribution, invoke) }, contribution.actionId);
54583
54812
  }
54584
54813
  const Icon2 = contribution.icon;
54585
54814
  const label = t(contribution.titleKey);
@@ -54592,7 +54821,7 @@ function ActionSlot({
54592
54821
  title: label,
54593
54822
  onClick: invoke,
54594
54823
  children: [
54595
- Icon2 && /* @__PURE__ */ jsx37(Icon2, { className: "size-4 shrink-0" }),
54824
+ Icon2 && /* @__PURE__ */ jsx38(Icon2, { className: "size-4 shrink-0" }),
54596
54825
  !hideLabels && label
54597
54826
  ]
54598
54827
  },
@@ -54609,37 +54838,37 @@ function ComponentSlot({
54609
54838
  (contribution) => contribution.kind === "component" && contribution.component
54610
54839
  );
54611
54840
  if (contributions2.length === 0) return null;
54612
- return /* @__PURE__ */ jsx37(Fragment14, { children: contributions2.map((contribution) => {
54841
+ return /* @__PURE__ */ jsx38(Fragment14, { children: contributions2.map((contribution) => {
54613
54842
  const Contributed = contribution.component;
54614
- return /* @__PURE__ */ jsx37(Contributed, { ...props ?? {} }, contribution.actionId);
54843
+ return /* @__PURE__ */ jsx38(Contributed, { ...props ?? {} }, contribution.actionId);
54615
54844
  }) });
54616
54845
  }
54617
54846
 
54618
54847
  // src/ui/plugin-host/PluginViewPlaceholder.tsx
54619
- import { useTranslation as useTranslation6 } from "react-i18next";
54848
+ import { useTranslation as useTranslation7 } from "react-i18next";
54620
54849
 
54621
54850
  // src/ui/plugins/open-plugins.ts
54622
54851
  var OPEN_PLUGINS_EVENT = "termix:open-plugins";
54623
54852
 
54624
54853
  // src/ui/plugin-host/PluginViewPlaceholder.tsx
54625
- import { jsx as jsx38, jsxs as jsxs12 } from "react/jsx-runtime";
54854
+ import { jsx as jsx39, jsxs as jsxs12 } from "react/jsx-runtime";
54626
54855
  function PluginViewPlaceholder({
54627
54856
  kind,
54628
54857
  viewId,
54629
54858
  compact = false
54630
54859
  }) {
54631
- const { t } = useTranslation6();
54860
+ const { t } = useTranslation7();
54632
54861
  usePluginStore();
54633
54862
  const { has } = usePermissions();
54634
54863
  const { status, owner } = unregisteredViewStatus(kind, viewId);
54635
54864
  const name = owner?.summary.name ?? viewId;
54636
54865
  if (status === "loading") {
54637
- return /* @__PURE__ */ jsx38(
54866
+ return /* @__PURE__ */ jsx39(
54638
54867
  "div",
54639
54868
  {
54640
54869
  className: "flex h-full w-full items-center justify-center",
54641
54870
  "aria-label": t("plugins.runtime.loading", { name }),
54642
- children: /* @__PURE__ */ jsx38("div", { className: "size-5 rounded-full border-2 border-muted-foreground/30 border-t-muted-foreground/70 animate-spin" })
54871
+ children: /* @__PURE__ */ jsx39("div", { className: "size-5 rounded-full border-2 border-muted-foreground/30 border-t-muted-foreground/70 animate-spin" })
54643
54872
  }
54644
54873
  );
54645
54874
  }
@@ -54652,10 +54881,10 @@ function PluginViewPlaceholder({
54652
54881
  "data-status": status,
54653
54882
  className: `flex h-full w-full flex-col items-center justify-center gap-2 text-center ${compact ? "p-3" : "p-6"}`,
54654
54883
  children: [
54655
- /* @__PURE__ */ jsx38(CircleSlash, { className: "size-6 text-muted-foreground/40" }),
54656
- /* @__PURE__ */ jsx38("span", { className: "text-sm font-semibold text-muted-foreground", children: title }),
54657
- /* @__PURE__ */ jsx38("span", { className: "text-xs text-muted-foreground/70", children: hint }),
54658
- !compact && status !== "failed" && has("admin.plugins.manage") && /* @__PURE__ */ jsx38(
54884
+ /* @__PURE__ */ jsx39(CircleSlash, { className: "size-6 text-muted-foreground/40" }),
54885
+ /* @__PURE__ */ jsx39("span", { className: "text-sm font-semibold text-muted-foreground", children: title }),
54886
+ /* @__PURE__ */ jsx39("span", { className: "text-xs text-muted-foreground/70", children: hint }),
54887
+ !compact && status !== "failed" && has("admin.plugins.manage") && /* @__PURE__ */ jsx39(
54659
54888
  Button,
54660
54889
  {
54661
54890
  variant: "outline",
@@ -54761,6 +54990,7 @@ export {
54761
54990
  notifyShellReady,
54762
54991
  resetShellBridge,
54763
54992
  usePluginScope,
54993
+ DocsLink,
54764
54994
  PANEL,
54765
54995
  PanelShell,
54766
54996
  BackButton,
@@ -54797,6 +55027,7 @@ export {
54797
55027
  isRedactedSecret,
54798
55028
  isFieldActive,
54799
55029
  getPlugins,
55030
+ PLUGINS_CHANGED_EVENT,
54800
55031
  useAreaPreferences,
54801
55032
  getPanel,
54802
55033
  listPanels,