@termix-ssh/plugin-sdk 1.0.13 → 1.0.17

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 (46) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/PLUGIN-GUIDE.md +104 -69
  3. package/dist/backend.d.ts +500 -1
  4. package/dist/backend.js +7 -0
  5. package/dist/capabilities.js +4 -0
  6. package/dist/frontend.d.ts +45 -0
  7. package/dist/host/{PluginsTab-OXQQ7M4I.js → PluginsTab-YAGYRBIE.js} +3 -3
  8. package/dist/host/{chunk-T2PZWXFJ.js → chunk-2MU6WNSX.js} +1 -1
  9. package/dist/host/{chunk-54KCK6IZ.js → chunk-446UEDSK.js} +6 -2
  10. package/dist/host/{chunk-CN5UBTBP.js → chunk-7LEJ3HYF.js} +49 -3
  11. package/dist/host/{chunk-CALNJCHN.js → chunk-J2AILBXM.js} +561 -400
  12. package/dist/host/{chunk-ERGN4YW6.js → chunk-LQKK2JJA.js} +5 -1
  13. package/dist/host/core-classes.json +1 -1
  14. package/dist/host/{linked-server-5VFO4XQB.js → linked-server-OUF3POT7.js} +5 -3
  15. package/dist/host/{rbac-api-5YMBJAO5.js → rbac-api-LZVENREG.js} +1 -1
  16. package/dist/host/testing-host.js +4 -4
  17. package/dist/host/types/src/types/index.d.ts +10 -0
  18. package/dist/host/types/src/types/onboarding.d.ts +0 -1
  19. package/dist/host/types/src/types/ui-types.d.ts +3 -1
  20. package/dist/host/types/src/ui/api/auth-methods-api.d.ts +2 -0
  21. package/dist/host/types/src/ui/api/settings-api.d.ts +6 -0
  22. package/dist/host/types/src/ui/api/ssh-host-management-api.d.ts +6 -0
  23. package/dist/host/types/src/ui/auth/Auth.d.ts +1 -0
  24. package/dist/host/types/src/ui/lib/linked-server.d.ts +2 -0
  25. package/dist/host/types/src/ui/main-axios.d.ts +2 -2
  26. package/dist/host/types/src/ui/plugin-host/NativeView.d.ts +19 -0
  27. package/dist/host/types/src/ui/plugin-host/sdk-ui.d.ts +4 -8
  28. package/dist/host/types/src/ui/shell/CompanionFrame.d.ts +11 -0
  29. package/dist/host/types/src/ui/shell/companions.d.ts +63 -0
  30. package/dist/host/types/src/ui/sidebar/AuthEnrollmentSections.d.ts +4 -1
  31. package/dist/host/ui.js +171 -820
  32. package/dist/host/{use-permissions-ISEDRY3J.js → use-permissions-BYCDS5PD.js} +1 -1
  33. package/dist/manifest.d.ts +1 -1
  34. package/dist/manifest.js +1 -1
  35. package/dist/testing.d.ts +26 -2
  36. package/dist/testing.js +89 -1
  37. package/dist/vitest-preset.js +23 -0
  38. package/package.json +1 -1
  39. package/schema/manifest.schema.json +4 -0
  40. package/dist/host/types/src/ui/api/sync-api.d.ts +0 -93
  41. package/dist/host/types/src/ui/auth/ElectronLoginForm.d.ts +0 -9
  42. package/dist/host/types/src/ui/hooks/use-sync-status.d.ts +0 -4
  43. package/dist/host/types/src/ui/lib/sync-status.d.ts +0 -6
  44. package/dist/host/types/src/ui/settings/sync/LinkServerDialog.d.ts +0 -12
  45. package/dist/host/types/src/ui/settings/sync/ProxySettingsFields.d.ts +0 -15
  46. package/dist/host/types/src/ui/settings/sync/saved-servers.d.ts +0 -3
@@ -3,14 +3,16 @@ import {
3
3
  hasPermission,
4
4
  resetPermissionsCache,
5
5
  usePermissions
6
- } from "./chunk-T2PZWXFJ.js";
6
+ } from "./chunk-2MU6WNSX.js";
7
7
  import {
8
+ ArrowDownToLine,
9
+ ArrowLeftToLine,
10
+ ArrowRightToLine,
8
11
  Button,
9
12
  Check,
10
13
  Checkbox,
11
14
  CheckboxIndicator,
12
15
  CircleSlash,
13
- Cloud,
14
16
  Columns2,
15
17
  DefaultsOnly,
16
18
  DocsLink,
@@ -27,6 +29,7 @@ import {
27
29
  OPEN_PLUGIN_SETTINGS_EVENT,
28
30
  PLUGINS_CHANGED_EVENT,
29
31
  PLUGIN_SETTINGS_CHANGED_EVENT,
32
+ PanelRight,
30
33
  Plug,
31
34
  PluginIcon,
32
35
  Puzzle,
@@ -80,7 +83,7 @@ import {
80
83
  withIconBoundary,
81
84
  withPluginContext,
82
85
  withPluginScope
83
- } from "./chunk-ERGN4YW6.js";
86
+ } from "./chunk-LQKK2JJA.js";
84
87
  import {
85
88
  SYNC_CHANGED_EVENT,
86
89
  TRUST_TOKEN_KEY,
@@ -118,7 +121,6 @@ import {
118
121
  logActivity,
119
122
  manifestDeclares,
120
123
  markUserAuthenticated,
121
- notifySyncChanged,
122
124
  parseCustomKeybindings,
123
125
  patchOpenTab,
124
126
  pluginStepKey,
@@ -130,7 +132,7 @@ import {
130
132
  usePluginStore,
131
133
  useTabTypes,
132
134
  websocketAuthProtocols
133
- } from "./chunk-CN5UBTBP.js";
135
+ } from "./chunk-7LEJ3HYF.js";
134
136
  import {
135
137
  define_import_meta_env_default,
136
138
  init_define_import_meta_env
@@ -268,6 +270,14 @@ async function getLoginMethods() {
268
270
  return [];
269
271
  }
270
272
  }
273
+ async function getRememberDeviceAllowed() {
274
+ try {
275
+ const response = await authApi.get("/users/auth/methods");
276
+ return response.data?.rememberDevice !== false;
277
+ } catch {
278
+ return true;
279
+ }
280
+ }
271
281
  async function submitLoginMethod(methodId, body, instanceId) {
272
282
  try {
273
283
  const response = await authApi.post(
@@ -1114,13 +1124,6 @@ var en_default = {
1114
1124
  pluginsLoadFailed: "The plugin list could not be loaded. You can choose plugins later from the Plugins tab.",
1115
1125
  securityTitle: "Secure your account",
1116
1126
  securityIntro: "Add a passkey or an authenticator app to your account. You can also do this later in Settings, under Security.",
1117
- desktopTitle: "How do you want to use Termix?",
1118
- desktopBody: "You can change this any time from Sync in the sidebar.",
1119
- desktopLocalTitle: "On this device only",
1120
- desktopLocalBody: "Everything is stored here. Nothing to set up.",
1121
- desktopLinkTitle: "With my Termix server",
1122
- desktopLinkBody: "Sign in to your server to use the same hosts and settings on every device.",
1123
- desktopLinked: "Linked to {{server}}",
1124
1127
  pluginsDocsLink: "Read about each one"
1125
1128
  },
1126
1129
  desktopUpdates: {
@@ -1141,6 +1144,11 @@ var en_default = {
1141
1144
  ready: "Termix {{version}} is ready to install",
1142
1145
  readyHint: "It installs the next time Termix restarts."
1143
1146
  },
1147
+ companion: {
1148
+ close: "Close panel",
1149
+ moveSide: "Move panel",
1150
+ resize: "Resize panel"
1151
+ },
1144
1152
  nav: {
1145
1153
  home: "Home",
1146
1154
  terminal: "Terminal",
@@ -1151,7 +1159,6 @@ var en_default = {
1151
1159
  hosts: "Hosts",
1152
1160
  credentials: "Credentials",
1153
1161
  connections: "Connections",
1154
- sync: "Sync",
1155
1162
  reconnectDisconnectedTerminals: "Reconnect all disconnected terminals",
1156
1163
  reconnectingTerminals_one: "Reconnecting {{count}} terminal",
1157
1164
  reconnectingTerminals_other: "Reconnecting {{count}} terminals",
@@ -1447,6 +1454,9 @@ var en_default = {
1447
1454
  deleteFolderConfirm_one: 'Delete "{{name}}" and its {{count}} host? This cannot be undone.',
1448
1455
  deleteFolderConfirm_other: 'Delete "{{name}}" and its {{count}} hosts? This cannot be undone.',
1449
1456
  failedToMoveHosts: "Failed to move hosts",
1457
+ managedBanner: "This host is managed by {{plugin}}. Change it there. You can still pin and reorder it.",
1458
+ managedTooltip: "Managed by {{plugin}}",
1459
+ addBlocked: "Hosts are managed by {{plugin}}, so new hosts can't be added here.",
1450
1460
  failedToReorder: "Failed to reorder",
1451
1461
  expandAll: "Expand all folders",
1452
1462
  collapseAll: "Collapse all folders",
@@ -2043,6 +2053,18 @@ var en_default = {
2043
2053
  title: "Handle sign in",
2044
2054
  consequence: "It can add a login method, a second factor or an SSH sign-in type."
2045
2055
  },
2056
+ "auth:policy": {
2057
+ title: "Set sign in rules",
2058
+ consequence: "It can sign people out when idle, limit session length, turn off remember me and require a second factor."
2059
+ },
2060
+ "ssh:transport": {
2061
+ title: "Choose how hosts are reached",
2062
+ consequence: "It can carry the SSH connection to a host over its own route, so it sees everything sent to and from those hosts, passwords included."
2063
+ },
2064
+ "sync:provide": {
2065
+ title: "Run sync",
2066
+ consequence: "It can answer all sync requests, so it sees the data that devices and servers sync."
2067
+ },
2046
2068
  "system:tls": {
2047
2069
  title: "Manage certificates",
2048
2070
  consequence: "It can replace the certificate Termix serves over HTTPS and answer domain checks from certificate authorities."
@@ -2111,6 +2133,10 @@ var en_default = {
2111
2133
  title: "Store recent activity",
2112
2134
  consequence: "Becomes the store for the recent activity list of every user."
2113
2135
  },
2136
+ "hosts:lock": {
2137
+ title: "Manage hosts as read only",
2138
+ consequence: "Hosts it manages can't be edited, deleted or reshared by anyone else."
2139
+ },
2114
2140
  "desktop:window": {
2115
2141
  title: "Open desktop windows",
2116
2142
  consequence: "Only in the Termix desktop app."
@@ -2507,87 +2533,6 @@ var en_default = {
2507
2533
  noData: "No data",
2508
2534
  filter: "Filter..."
2509
2535
  },
2510
- sync: {
2511
- minutesAgo_one: "{{count}} minute ago",
2512
- minutesAgo_other: "{{count}} minutes ago",
2513
- hoursAgo_one: "{{count}} hour ago",
2514
- hoursAgo_other: "{{count}} hours ago",
2515
- pending_one: "{{count}} change waiting to sync",
2516
- pending_other: "{{count}} changes waiting to sync",
2517
- proxy: {
2518
- headersTitle: "Extra headers",
2519
- headersHint: "Sent with every request to the server, for example a Cloudflare Access service token.",
2520
- headerName: "Header name",
2521
- headerValue: "Value",
2522
- savedValue: "Saved, type to change",
2523
- addHeader: "Add header",
2524
- removeHeader: "Remove header",
2525
- basicAuthTitle: "Username and password",
2526
- basicAuthHint: "For a proxy that asks for a username and password before Termix.",
2527
- username: "Username",
2528
- password: "Password",
2529
- allowInvalidCertificate: "Trust a self-signed certificate",
2530
- allowInvalidCertificateHint: "Only for your own server, when its certificate is not from a known authority."
2531
- },
2532
- problems: {
2533
- invalidUrl: "That does not look like a web address.",
2534
- unreachable: "Could not reach the server. Check the address and that the server is running.",
2535
- unreachableWithReason: "Could not reach the server: {{reason}}",
2536
- tlsUntrusted: "The server certificate is not trusted. If this is your own server with a self-signed certificate, turn on the option below.",
2537
- basicAuthRequired: "Something in front of the server asks for a username and password. Enter them below.",
2538
- proxyLogin: "A login page or proxy answered instead of Termix. Add the headers it needs below, or check the address.",
2539
- notTermix: "There is no Termix server at that address. If it runs under a path, include it, like https://example.com/termix.",
2540
- versionMismatch: "That server runs an older Termix. Update it to 2.9 or newer to link.",
2541
- differentAccount: "You signed in to a different account. Sign in to the account this device is linked to, or unlink first."
2542
- },
2543
- wizard: {
2544
- title: "Link to a Termix server",
2545
- reloginTitle: "Sign in again",
2546
- savedServers: "Recent servers",
2547
- removeServer: "Remove from the list",
2548
- serverDescription: "Enter the address you use to open Termix in your browser.",
2549
- signinDescription: "Sign in to {{url}}",
2550
- chooseDescription: "This device already has data. What should happen to it?",
2551
- finishingDescription: "Linking this device.",
2552
- serverUrl: "Server address",
2553
- serverUrlHint: "For example https://termix.example.com",
2554
- showProxySettings: "Reverse proxy settings",
2555
- hideProxySettings: "Hide reverse proxy settings",
2556
- continue: "Continue",
2557
- back: "Back",
2558
- mergeTitle: "Add it to my account",
2559
- mergeBody_one: "The item on this device is added to your account. Items your account already has stay as they are on the server.",
2560
- mergeBody_other: "The {{count}} items on this device are added to your account. Items your account already has stay as they are on the server.",
2561
- replaceTitle: "Replace it with my account",
2562
- replaceBody_one: "The item on this device is removed, and this device takes everything from your account. Hosts kept on this device only stay.",
2563
- replaceBody_other: "The {{count}} items on this device are removed, and this device takes everything from your account. Hosts kept on this device only stay.",
2564
- link: "Link",
2565
- linking: "Linking and running the first sync...",
2566
- linked: "Linked. Your data is syncing now.",
2567
- signedInAgain: "Signed in. Sync is running again."
2568
- },
2569
- entities: {
2570
- hosts: "Hosts",
2571
- sshCredentials: "Credentials",
2572
- sshFolders: "Folders",
2573
- userPreferences: "Preferences",
2574
- pluginUserSettings: "Plugin settings",
2575
- sharedHosts: "Hosts shared with you",
2576
- sharedCredentials: "Credentials shared with you",
2577
- vaultProfiles: "Vault profiles",
2578
- snippetFolders: "Snippet folders",
2579
- snippets: "Snippets",
2580
- dashboardServiceLinks: "Service links",
2581
- homepageItems: "Homepage items",
2582
- networkTopology: "Network map",
2583
- workspaces: "Workspaces",
2584
- hostDefaults: "Host defaults",
2585
- hostDefaultsAdmin: "Server host defaults",
2586
- aiProviders: "AI providers",
2587
- homepageLayouts: "Homepage layouts",
2588
- accountProfile: "Account profile"
2589
- }
2590
- },
2591
2536
  hostDefaults: {
2592
2537
  source: {
2593
2538
  folder: "Folder {{name}}",
@@ -2648,8 +2593,8 @@ var localeLoaders = Object.fromEntries(
2648
2593
  Object.entries(LOCALE_FILES).map(([code, file]) => [
2649
2594
  code,
2650
2595
  () => {
2651
- const load = translatedFiles[`../locales/translated/${file}.json`];
2652
- return load ? load() : Promise.reject(new Error(`no ${file}`));
2596
+ const load2 = translatedFiles[`../locales/translated/${file}.json`];
2597
+ return load2 ? load2() : Promise.reject(new Error(`no ${file}`));
2653
2598
  }
2654
2599
  ])
2655
2600
  );
@@ -2752,130 +2697,6 @@ function consumeLoginLanguage() {
2752
2697
  }
2753
2698
  var i18n_default = i18n;
2754
2699
 
2755
- // src/ui/api/sync-api.ts
2756
- init_define_import_meta_env();
2757
- async function getSyncStatus() {
2758
- return (await authApi.get("/sync/link/status")).data;
2759
- }
2760
- async function probeServer(target) {
2761
- return (await authApi.post("/sync/link/probe", target)).data;
2762
- }
2763
- async function getLinkPreview() {
2764
- return (await authApi.get("/sync/link/preview")).data?.counts ?? {};
2765
- }
2766
- async function completeLink(input) {
2767
- return (await authApi.post("/sync/link/complete", input)).data;
2768
- }
2769
- async function reloginLink(token) {
2770
- return (await authApi.post("/sync/link/relogin", { token })).data;
2771
- }
2772
- async function unlinkServer(keepData) {
2773
- return (await authApi.post("/sync/link/unlink", { keepData })).data;
2774
- }
2775
- async function syncNow() {
2776
- return (await authApi.post("/sync/link/sync", {}, { timeout: 3e5 })).data;
2777
- }
2778
- async function updateSyncSettings(settings) {
2779
- return (await authApi.put("/sync/link/settings", settings)).data;
2780
- }
2781
- async function getSyncConflicts() {
2782
- return (await authApi.get("/sync/link/conflicts")).data?.conflicts ?? [];
2783
- }
2784
- async function settleSyncConflict(id, keep) {
2785
- await authApi.post(`/sync/link/conflicts/${id}`, { keep });
2786
- }
2787
- async function getSyncErrors() {
2788
- return (await authApi.get("/sync/link/errors")).data?.errors ?? [];
2789
- }
2790
- async function retrySyncErrors() {
2791
- return (await authApi.post("/sync/link/errors/retry", {}, { timeout: 3e5 })).data;
2792
- }
2793
-
2794
- // src/ui/lib/sync-status.ts
2795
- init_define_import_meta_env();
2796
- var IDLE_MS = 15e3;
2797
- var BUSY_MS = 2e3;
2798
- var snapshot = null;
2799
- var timer = null;
2800
- var lastSyncAt;
2801
- var listeners = /* @__PURE__ */ new Set();
2802
- function emit() {
2803
- for (const listener of listeners) listener();
2804
- }
2805
- function announce() {
2806
- notifySyncChanged();
2807
- window.dispatchEvent(new CustomEvent("hosts:refresh"));
2808
- window.dispatchEvent(new CustomEvent("termix:hosts-changed"));
2809
- window.dispatchEvent(new CustomEvent("termix:credentials-changed"));
2810
- window.dispatchEvent(new CustomEvent("termix:plugins-changed"));
2811
- }
2812
- async function poll() {
2813
- timer = null;
2814
- try {
2815
- const next = await getSyncStatus();
2816
- const syncedAt = next.lastSyncAt ?? null;
2817
- if (lastSyncAt !== void 0 && syncedAt !== lastSyncAt) announce();
2818
- if (snapshot && snapshot.linked !== next.linked) notifySyncChanged();
2819
- lastSyncAt = syncedAt;
2820
- snapshot = next;
2821
- emit();
2822
- } catch {
2823
- }
2824
- schedule();
2825
- }
2826
- function schedule(delay) {
2827
- if (listeners.size === 0 || timer) return;
2828
- const busy = snapshot?.status === "syncing" || !!snapshot?.pending;
2829
- timer = setTimeout(() => void poll(), delay ?? (busy ? BUSY_MS : IDLE_MS));
2830
- }
2831
- function subscribeSyncStatus(listener) {
2832
- listeners.add(listener);
2833
- if (isElectron() && listeners.size === 1 && !timer) void poll();
2834
- return () => {
2835
- listeners.delete(listener);
2836
- if (listeners.size === 0 && timer) {
2837
- clearTimeout(timer);
2838
- timer = null;
2839
- }
2840
- };
2841
- }
2842
- function getSyncStatusSnapshot() {
2843
- return snapshot;
2844
- }
2845
- function setSyncStatus(next) {
2846
- snapshot = next;
2847
- if (lastSyncAt !== void 0 && (next.lastSyncAt ?? null) !== lastSyncAt) {
2848
- announce();
2849
- }
2850
- lastSyncAt = next.lastSyncAt ?? null;
2851
- emit();
2852
- if (timer) clearTimeout(timer);
2853
- timer = null;
2854
- schedule(BUSY_MS);
2855
- }
2856
- function refreshSyncStatus() {
2857
- if (timer) clearTimeout(timer);
2858
- timer = null;
2859
- void poll();
2860
- }
2861
-
2862
- // src/ui/hooks/use-sync-status.ts
2863
- init_define_import_meta_env();
2864
- import { useSyncExternalStore } from "react";
2865
- function useSyncStatus() {
2866
- return useSyncExternalStore(
2867
- subscribeSyncStatus,
2868
- getSyncStatusSnapshot,
2869
- () => null
2870
- );
2871
- }
2872
- function useSyncAttentionCount() {
2873
- const status = useSyncStatus();
2874
- if (!status?.linked) return null;
2875
- const count = (status.conflicts ?? 0) + (status.errors ?? 0) + (status.status === "signed_out" ? 1 : 0);
2876
- return count || null;
2877
- }
2878
-
2879
2700
  // src/ui/shell/panel-registry.ts
2880
2701
  init_define_import_meta_env();
2881
2702
  var registry = createRegistry();
@@ -2887,7 +2708,7 @@ var resetPanels = registry.reset;
2887
2708
 
2888
2709
  // src/ui/sidebar/rail-items.ts
2889
2710
  init_define_import_meta_env();
2890
- import { useMemo, useSyncExternalStore as useSyncExternalStore2 } from "react";
2711
+ import { useMemo, useSyncExternalStore } from "react";
2891
2712
  var RAIL_GROUP_ORDER = ["objects", "tools", "system"];
2892
2713
  var RAIL_ITEMS = [
2893
2714
  {
@@ -2920,15 +2741,6 @@ var RAIL_ITEMS = [
2920
2741
  mobilePrimary: true,
2921
2742
  group: "tools"
2922
2743
  },
2923
- {
2924
- id: "sync",
2925
- icon: Cloud,
2926
- labelKey: "nav.sync",
2927
- electronOnly: true,
2928
- placement: "footer",
2929
- group: "system",
2930
- useBadge: useSyncAttentionCount
2931
- },
2932
2744
  {
2933
2745
  id: "plugins",
2934
2746
  icon: Puzzle,
@@ -2987,7 +2799,7 @@ function permittedRailItems(items, permissions) {
2987
2799
  );
2988
2800
  }
2989
2801
  function useRailItems() {
2990
- const items = useSyncExternalStore2(
2802
+ const items = useSyncExternalStore(
2991
2803
  registeredRailItems.subscribe,
2992
2804
  railItemsSnapshot,
2993
2805
  railItemsSnapshot
@@ -3352,12 +3164,12 @@ var resetDashboardCards = registry3.reset;
3352
3164
  init_define_import_meta_env();
3353
3165
  var points = /* @__PURE__ */ new Map();
3354
3166
  function point(id) {
3355
- let registry5 = points.get(id);
3356
- if (!registry5) {
3357
- registry5 = createRegistry();
3358
- points.set(id, registry5);
3167
+ let registry6 = points.get(id);
3168
+ if (!registry6) {
3169
+ registry6 = createRegistry();
3170
+ points.set(id, registry6);
3359
3171
  }
3360
- return registry5;
3172
+ return registry6;
3361
3173
  }
3362
3174
  function registerExtension(pointId, extension) {
3363
3175
  return point(pointId).register(extension);
@@ -3398,21 +3210,21 @@ init_define_import_meta_env();
3398
3210
  var actions3 = /* @__PURE__ */ new Map();
3399
3211
  var slots = /* @__PURE__ */ new Map();
3400
3212
  var contributions = /* @__PURE__ */ new Map();
3401
- var listeners2 = /* @__PURE__ */ new Set();
3213
+ var listeners = /* @__PURE__ */ new Set();
3402
3214
  var snapshots = /* @__PURE__ */ new Map();
3403
3215
  var EMPTY = [];
3404
- function emit2() {
3216
+ function emit() {
3405
3217
  snapshots.clear();
3406
- for (const listener of listeners2) listener();
3218
+ for (const listener of listeners) listener();
3407
3219
  }
3408
3220
  function registerAction(id, handler, options = {}) {
3409
3221
  const action = { id, handler, ...options };
3410
3222
  actions3.set(id, action);
3411
- emit2();
3223
+ emit();
3412
3224
  return () => {
3413
3225
  if (actions3.get(id) === action) {
3414
3226
  actions3.delete(id);
3415
- emit2();
3227
+ emit();
3416
3228
  }
3417
3229
  };
3418
3230
  }
@@ -3432,11 +3244,11 @@ async function invokeAction(id, ...args) {
3432
3244
  }
3433
3245
  function declareActionSlot(slot) {
3434
3246
  slots.set(slot.id, slot);
3435
- emit2();
3247
+ emit();
3436
3248
  return () => {
3437
3249
  if (slots.get(slot.id) === slot) {
3438
3250
  slots.delete(slot.id);
3439
- emit2();
3251
+ emit();
3440
3252
  }
3441
3253
  };
3442
3254
  }
@@ -3457,7 +3269,7 @@ function registerSlotContribution(slotId, contribution) {
3457
3269
  }
3458
3270
  const stored = { ...contribution, kind };
3459
3271
  forSlot.set(contribution.actionId, stored);
3460
- emit2();
3272
+ emit();
3461
3273
  return () => {
3462
3274
  if (contributions.get(slotId)?.get(contribution.actionId) === stored) {
3463
3275
  unregisterSlotContribution(slotId, contribution.actionId);
@@ -3468,7 +3280,7 @@ function unregisterSlotContribution(slotId, actionId) {
3468
3280
  const forSlot = contributions.get(slotId);
3469
3281
  if (!forSlot?.delete(actionId)) return;
3470
3282
  if (forSlot.size === 0) contributions.delete(slotId);
3471
- emit2();
3283
+ emit();
3472
3284
  }
3473
3285
  function getSlotContributions(slotId) {
3474
3286
  const cached = snapshots.get(slotId);
@@ -3485,9 +3297,9 @@ function getSlotContributions(slotId) {
3485
3297
  return sorted;
3486
3298
  }
3487
3299
  function subscribeToActionRegistry(listener) {
3488
- listeners2.add(listener);
3300
+ listeners.add(listener);
3489
3301
  return () => {
3490
- listeners2.delete(listener);
3302
+ listeners.delete(listener);
3491
3303
  };
3492
3304
  }
3493
3305
  function resetActionRegistry() {
@@ -3495,7 +3307,7 @@ function resetActionRegistry() {
3495
3307
  slots.clear();
3496
3308
  contributions.clear();
3497
3309
  snapshots.clear();
3498
- emit2();
3310
+ emit();
3499
3311
  }
3500
3312
 
3501
3313
  // src/ui/lib/plugin-transport.ts
@@ -3593,14 +3405,14 @@ async function pluginWsUrl(pluginId, path2, options = {}) {
3593
3405
  init_define_import_meta_env();
3594
3406
  import {
3595
3407
  createElement,
3596
- useSyncExternalStore as useSyncExternalStore3
3408
+ useSyncExternalStore as useSyncExternalStore2
3597
3409
  } from "react";
3598
3410
  var components = /* @__PURE__ */ new Map();
3599
- var listeners3 = /* @__PURE__ */ new Set();
3411
+ var listeners2 = /* @__PURE__ */ new Set();
3600
3412
  var version = 0;
3601
3413
  function notify() {
3602
3414
  version += 1;
3603
- for (const listener of listeners3) listener();
3415
+ for (const listener of listeners2) listener();
3604
3416
  }
3605
3417
  function registerPluginComponent(id, component) {
3606
3418
  components.set(id, component);
@@ -3613,11 +3425,11 @@ function registerPluginComponent(id, component) {
3613
3425
  };
3614
3426
  }
3615
3427
  function subscribe(listener) {
3616
- listeners3.add(listener);
3617
- return () => listeners3.delete(listener);
3428
+ listeners2.add(listener);
3429
+ return () => listeners2.delete(listener);
3618
3430
  }
3619
3431
  function usePluginComponent(id) {
3620
- useSyncExternalStore3(subscribe, () => version);
3432
+ useSyncExternalStore2(subscribe, () => version);
3621
3433
  return components.get(id);
3622
3434
  }
3623
3435
  function PluginComponent({
@@ -3711,7 +3523,7 @@ import { createContext, useContext, useEffect as useEffect3, useState as useStat
3711
3523
 
3712
3524
  // src/ui/look/theme-registry.ts
3713
3525
  init_define_import_meta_env();
3714
- import { useSyncExternalStore as useSyncExternalStore4 } from "react";
3526
+ import { useSyncExternalStore as useSyncExternalStore3 } from "react";
3715
3527
  var SYSTEM_THEME = "system";
3716
3528
  var BUILTIN_THEMES = [
3717
3529
  {
@@ -3730,23 +3542,23 @@ var BUILTIN_THEMES = [
3730
3542
  var builtins = new Map(BUILTIN_THEMES.map((theme) => [theme.id, theme]));
3731
3543
  var pluginThemes = /* @__PURE__ */ new Map();
3732
3544
  var pluginThemesKnown = false;
3733
- var listeners4 = /* @__PURE__ */ new Set();
3734
- var snapshot2 = null;
3735
- function emit3() {
3736
- snapshot2 = null;
3737
- for (const listener of listeners4) listener();
3545
+ var listeners3 = /* @__PURE__ */ new Set();
3546
+ var snapshot = null;
3547
+ function emit2() {
3548
+ snapshot = null;
3549
+ for (const listener of listeners3) listener();
3738
3550
  }
3739
3551
  function listThemes() {
3740
- if (!snapshot2) {
3552
+ if (!snapshot) {
3741
3553
  const replaced = BUILTIN_THEMES.map(
3742
3554
  (theme) => pluginThemes.get(theme.id) ?? theme
3743
3555
  );
3744
3556
  const extra = [...pluginThemes.values()].filter(
3745
3557
  (theme) => !builtins.has(theme.id)
3746
3558
  );
3747
- snapshot2 = [...replaced, ...extra];
3559
+ snapshot = [...replaced, ...extra];
3748
3560
  }
3749
- return snapshot2;
3561
+ return snapshot;
3750
3562
  }
3751
3563
  function getTheme(id) {
3752
3564
  return pluginThemes.get(id) ?? builtins.get(id);
@@ -3761,16 +3573,16 @@ function setPluginThemes(themes) {
3761
3573
  });
3762
3574
  if (same && !first) return;
3763
3575
  pluginThemes = next;
3764
- emit3();
3576
+ emit2();
3765
3577
  }
3766
3578
  function subscribeThemes(listener) {
3767
- listeners4.add(listener);
3579
+ listeners3.add(listener);
3768
3580
  return () => {
3769
- listeners4.delete(listener);
3581
+ listeners3.delete(listener);
3770
3582
  };
3771
3583
  }
3772
3584
  function useThemes() {
3773
- return useSyncExternalStore4(subscribeThemes, listThemes, listThemes);
3585
+ return useSyncExternalStore3(subscribeThemes, listThemes, listThemes);
3774
3586
  }
3775
3587
  function themeMode(id) {
3776
3588
  if (id === SYSTEM_THEME) {
@@ -3954,9 +3766,9 @@ function useSshAuthProviders() {
3954
3766
 
3955
3767
  // src/ui/hooks/use-action-slot.ts
3956
3768
  init_define_import_meta_env();
3957
- import { useMemo as useMemo3, useSyncExternalStore as useSyncExternalStore5 } from "react";
3769
+ import { useMemo as useMemo3, useSyncExternalStore as useSyncExternalStore4 } from "react";
3958
3770
  function useActionSlot(slotId, context) {
3959
- const contributions2 = useSyncExternalStore5(
3771
+ const contributions2 = useSyncExternalStore4(
3960
3772
  subscribeToActionRegistry,
3961
3773
  () => getSlotContributions(slotId),
3962
3774
  () => getSlotContributions(slotId)
@@ -4103,6 +3915,7 @@ function sshHostToHost(h) {
4103
3915
  connectionOrigin: h.connectionOrigin ?? null,
4104
3916
  localOnly: !!h.localOnly,
4105
3917
  sharedCopy: !!h.sharedCopy,
3918
+ ...h.lock ? { lock: h.lock } : {},
4106
3919
  enableSsh: h.enableSsh != null ? h.enableSsh : isSshHost,
4107
3920
  sshPort: h.sshPort ?? (h.connectionType === "ssh" || !h.connectionType ? h.port : 22),
4108
3921
  protocolAuth: h.protocolAuth ?? {},
@@ -4944,28 +4757,28 @@ function restoreSplitTabs(persisted, tabs, openedAt = Date.now()) {
4944
4757
 
4945
4758
  // src/ui/shell/split/split-drag.ts
4946
4759
  init_define_import_meta_env();
4947
- import { useSyncExternalStore as useSyncExternalStore6 } from "react";
4760
+ import { useSyncExternalStore as useSyncExternalStore5 } from "react";
4948
4761
  var state = null;
4949
4762
  var hitTest = null;
4950
4763
  var dropHandler = null;
4951
- var listeners5 = /* @__PURE__ */ new Set();
4952
- function emit4() {
4953
- for (const listener of listeners5) listener();
4764
+ var listeners4 = /* @__PURE__ */ new Set();
4765
+ function emit3() {
4766
+ for (const listener of listeners4) listener();
4954
4767
  }
4955
4768
  function beginSplitDrag(source, x, y) {
4956
4769
  state = { source, x, y, hover: hitTest?.(x, y, source) ?? null };
4957
- emit4();
4770
+ emit3();
4958
4771
  }
4959
4772
  function moveSplitDrag(x, y) {
4960
4773
  if (!state) return;
4961
4774
  state = { ...state, x, y, hover: hitTest?.(x, y, state.source) ?? null };
4962
- emit4();
4775
+ emit3();
4963
4776
  }
4964
4777
  function endSplitDrag(commit) {
4965
4778
  const ended = state;
4966
4779
  if (!ended) return;
4967
4780
  state = null;
4968
- emit4();
4781
+ emit3();
4969
4782
  if (commit && ended.hover) dropHandler?.(ended.source, ended.hover);
4970
4783
  }
4971
4784
  function isSplitDragging() {
@@ -4984,11 +4797,11 @@ function setSplitDropHandler(next) {
4984
4797
  };
4985
4798
  }
4986
4799
  function subscribe2(listener) {
4987
- listeners5.add(listener);
4988
- return () => listeners5.delete(listener);
4800
+ listeners4.add(listener);
4801
+ return () => listeners4.delete(listener);
4989
4802
  }
4990
4803
  function useSplitDrag() {
4991
- return useSyncExternalStore6(
4804
+ return useSyncExternalStore5(
4992
4805
  subscribe2,
4993
4806
  () => state,
4994
4807
  () => null
@@ -5027,18 +4840,18 @@ function dropTargetRect(rect, target) {
5027
4840
 
5028
4841
  // src/ui/shell/split/split-targets.ts
5029
4842
  init_define_import_meta_env();
5030
- import { useSyncExternalStore as useSyncExternalStore7 } from "react";
4843
+ import { useSyncExternalStore as useSyncExternalStore6 } from "react";
5031
4844
  var EMPTY2 = { splits: [], canStartSplit: false };
5032
- var snapshot3 = EMPTY2;
4845
+ var snapshot2 = EMPTY2;
5033
4846
  var snapshotKey = "";
5034
4847
  var opener = null;
5035
- var listeners6 = /* @__PURE__ */ new Set();
4848
+ var listeners5 = /* @__PURE__ */ new Set();
5036
4849
  function publishSplitTargets(next) {
5037
4850
  const key = JSON.stringify(next);
5038
4851
  if (key === snapshotKey) return;
5039
4852
  snapshotKey = key;
5040
- snapshot3 = next;
5041
- for (const listener of listeners6) listener();
4853
+ snapshot2 = next;
4854
+ for (const listener of listeners5) listener();
5042
4855
  }
5043
4856
  function setSplitOpener(next) {
5044
4857
  opener = next;
@@ -5054,13 +4867,13 @@ function openInSplit(target, open) {
5054
4867
  else open();
5055
4868
  }
5056
4869
  function subscribe3(listener) {
5057
- listeners6.add(listener);
5058
- return () => listeners6.delete(listener);
4870
+ listeners5.add(listener);
4871
+ return () => listeners5.delete(listener);
5059
4872
  }
5060
4873
  function useSplitTargets() {
5061
- return useSyncExternalStore7(
4874
+ return useSyncExternalStore6(
5062
4875
  subscribe3,
5063
- () => snapshot3,
4876
+ () => snapshot2,
5064
4877
  () => EMPTY2
5065
4878
  );
5066
4879
  }
@@ -5102,25 +4915,25 @@ function buildLayoutTabSnapshots(tabs, genSlotId = createId) {
5102
4915
  }));
5103
4916
  return { snapshots: snapshots2, slotIdByTabId };
5104
4917
  }
5105
- function snapshotData(snapshot4) {
5106
- if (snapshot4.data) return snapshot4.data;
4918
+ function snapshotData(snapshot3) {
4919
+ if (snapshot3.data) return snapshot3.data;
5107
4920
  const legacy = {};
5108
- if (snapshot4.fleetId !== void 0) legacy.fleetId = snapshot4.fleetId;
5109
- if (snapshot4.initialFilePath)
5110
- legacy.initialFilePath = snapshot4.initialFilePath;
5111
- if (snapshot4.initialPath) legacy.initialPath = snapshot4.initialPath;
4921
+ if (snapshot3.fleetId !== void 0) legacy.fleetId = snapshot3.fleetId;
4922
+ if (snapshot3.initialFilePath)
4923
+ legacy.initialFilePath = snapshot3.initialFilePath;
4924
+ if (snapshot3.initialPath) legacy.initialPath = snapshot3.initialPath;
5112
4925
  return Object.keys(legacy).length > 0 ? legacy : void 0;
5113
4926
  }
5114
- function resolveLayoutTabTarget(snapshot4, allHosts) {
4927
+ function resolveLayoutTabTarget(snapshot3, allHosts) {
5115
4928
  let host;
5116
- if (snapshot4.hostSyncId) {
5117
- host = allHosts.find((h) => h.syncId === snapshot4.hostSyncId);
4929
+ if (snapshot3.hostSyncId) {
4930
+ host = allHosts.find((h) => h.syncId === snapshot3.hostSyncId);
5118
4931
  if (!host) return { kind: "skip" };
5119
4932
  }
5120
- if (opensAsSingleton(snapshot4.type, !!host)) {
4933
+ if (opensAsSingleton(snapshot3.type, !!host)) {
5121
4934
  return { kind: "singleton", host };
5122
4935
  }
5123
- if (!host && isUnregisteredPluginTabType(snapshot4.type)) {
4936
+ if (!host && isUnregisteredPluginTabType(snapshot3.type)) {
5124
4937
  return { kind: "singleton" };
5125
4938
  }
5126
4939
  return host ? { kind: "host", host } : { kind: "skip" };
@@ -5400,11 +5213,11 @@ function createTabStageStore() {
5400
5213
  const paneEls = /* @__PURE__ */ new Map();
5401
5214
  const listeners9 = /* @__PURE__ */ new Set();
5402
5215
  let normalView = null;
5403
- let version3 = 0;
5216
+ let version4 = 0;
5404
5217
  let order2 = [];
5405
5218
  let lastAnimatedTabId = null;
5406
5219
  const bump = () => {
5407
- version3 += 1;
5220
+ version4 += 1;
5408
5221
  for (const listener of listeners9) listener();
5409
5222
  };
5410
5223
  function getNode(tabId, ownBackground) {
@@ -5512,7 +5325,7 @@ function createTabStageStore() {
5512
5325
  listeners9.delete(listener);
5513
5326
  };
5514
5327
  },
5515
- version: () => version3
5328
+ version: () => version4
5516
5329
  };
5517
5330
  }
5518
5331
 
@@ -6888,7 +6701,7 @@ function useSplit({
6888
6701
  useEffect9(() => {
6889
6702
  if (!focusedPaneId || !focusedPaneTabId) return;
6890
6703
  let attempts = 0;
6891
- let timer2;
6704
+ let timer;
6892
6705
  const attempt = () => {
6893
6706
  const focused = document.activeElement;
6894
6707
  const paneEl = paneEls.get(focusedPaneId);
@@ -6904,12 +6717,12 @@ function useSplit({
6904
6717
  handle.focus();
6905
6718
  return;
6906
6719
  }
6907
- if (attempts++ < 10) timer2 = setTimeout(attempt, 100);
6720
+ if (attempts++ < 10) timer = setTimeout(attempt, 100);
6908
6721
  };
6909
6722
  const frame = requestAnimationFrame(attempt);
6910
6723
  return () => {
6911
6724
  cancelAnimationFrame(frame);
6912
- if (timer2) clearTimeout(timer2);
6725
+ if (timer) clearTimeout(timer);
6913
6726
  };
6914
6727
  }, [focusedPaneId, focusedPaneTabId]);
6915
6728
  useEffect9(() => {
@@ -7027,34 +6840,34 @@ function useWorkspaceLayout(tabsState, regions) {
7027
6840
  try {
7028
6841
  const slotIdToNewTabId = /* @__PURE__ */ new Map();
7029
6842
  const skippedTabs = [];
7030
- for (const snapshot4 of payload.tabs) {
7031
- const target = resolveLayoutTabTarget(snapshot4, hostsRef.current);
6843
+ for (const snapshot3 of payload.tabs) {
6844
+ const target = resolveLayoutTabTarget(snapshot3, hostsRef.current);
7032
6845
  if (target.kind === "skip") {
7033
- skippedTabs.push(snapshot4.hostNameSnapshot || snapshot4.label);
6846
+ skippedTabs.push(snapshot3.hostNameSnapshot || snapshot3.label);
7034
6847
  continue;
7035
6848
  }
7036
6849
  if (target.kind === "singleton") {
7037
6850
  const newTabId = tabsState.openSingletonTab(
7038
- snapshot4.type,
6851
+ snapshot3.type,
7039
6852
  void 0,
7040
6853
  target.host,
7041
- snapshotData(snapshot4)
6854
+ snapshotData(snapshot3)
7042
6855
  );
7043
- slotIdToNewTabId.set(snapshot4.slotId, newTabId ?? snapshot4.type);
6856
+ slotIdToNewTabId.set(snapshot3.slotId, newTabId ?? snapshot3.type);
7044
6857
  continue;
7045
6858
  }
7046
6859
  if (target.kind === "host") {
7047
6860
  const newTabId = tabsState.openTab(
7048
6861
  target.host,
7049
- snapshot4.type,
6862
+ snapshot3.type,
7050
6863
  {
7051
6864
  instanceId: createId(),
7052
6865
  restoredSessionId: null,
7053
- savedLabel: snapshot4.customLabel ?? snapshot4.label
6866
+ savedLabel: snapshot3.customLabel ?? snapshot3.label
7054
6867
  },
7055
- { data: snapshotData(snapshot4) }
6868
+ { data: snapshotData(snapshot3) }
7056
6869
  );
7057
- slotIdToNewTabId.set(snapshot4.slotId, newTabId);
6870
+ slotIdToNewTabId.set(snapshot3.slotId, newTabId);
7058
6871
  }
7059
6872
  }
7060
6873
  const splitIdBySlotId = /* @__PURE__ */ new Map();
@@ -7141,7 +6954,7 @@ function useShellKeybindings({
7141
6954
  toggleFullscreenRef.current = toggleFullscreen;
7142
6955
  useEffect11(() => {
7143
6956
  let cancelled = false;
7144
- const load = () => {
6957
+ const load2 = () => {
7145
6958
  getUserPreferences().then((prefs) => {
7146
6959
  if (cancelled) return;
7147
6960
  globalKeybindingsRef.current = parseCustomKeybindings(
@@ -7150,11 +6963,11 @@ function useShellKeybindings({
7150
6963
  }).catch(() => {
7151
6964
  });
7152
6965
  };
7153
- load();
7154
- window.addEventListener("customKeybindingsChanged", load);
6966
+ load2();
6967
+ window.addEventListener("customKeybindingsChanged", load2);
7155
6968
  return () => {
7156
6969
  cancelled = true;
7157
- window.removeEventListener("customKeybindingsChanged", load);
6970
+ window.removeEventListener("customKeybindingsChanged", load2);
7158
6971
  };
7159
6972
  }, []);
7160
6973
  useEffect11(() => {
@@ -7519,19 +7332,19 @@ function useShellLifecycle({
7519
7332
 
7520
7333
  // src/ui/shell/controller/shell-store.ts
7521
7334
  init_define_import_meta_env();
7522
- import { useSyncExternalStore as useSyncExternalStore8 } from "react";
7335
+ import { useSyncExternalStore as useSyncExternalStore7 } from "react";
7523
7336
  var current = null;
7524
- var listeners7 = /* @__PURE__ */ new Set();
7337
+ var listeners6 = /* @__PURE__ */ new Set();
7525
7338
  function publishShellController(next) {
7526
7339
  if (next === current) return;
7527
7340
  current = next;
7528
- for (const listener of listeners7) listener();
7341
+ for (const listener of listeners6) listener();
7529
7342
  }
7530
7343
  function useShellSnapshot() {
7531
- return useSyncExternalStore8(
7344
+ return useSyncExternalStore7(
7532
7345
  (listener) => {
7533
- listeners7.add(listener);
7534
- return () => listeners7.delete(listener);
7346
+ listeners6.add(listener);
7347
+ return () => listeners6.delete(listener);
7535
7348
  },
7536
7349
  () => current,
7537
7350
  () => current
@@ -7602,7 +7415,6 @@ function ShellControllerProvider({
7602
7415
  });
7603
7416
  openSingletonRef.current = tabsState.openSingletonTab;
7604
7417
  const { tabs, activeTabId } = tabsState;
7605
- useSyncStatus();
7606
7418
  const palette = {
7607
7419
  isOpen: paletteOpen,
7608
7420
  open: () => setPaletteOpen(true),
@@ -7801,25 +7613,25 @@ function computeAdaptivePollDelay(policy, state2, random = Math.random) {
7801
7613
  const jitter = base * jitterRatio * (random() * 2 - 1);
7802
7614
  return Math.max(min, Math.min(max, Math.round(base + jitter)));
7803
7615
  }
7804
- function runAdaptivePolling(poll2, policy, options = {}) {
7616
+ function runAdaptivePolling(poll, policy, options = {}) {
7805
7617
  const enabled = options.enabled ?? (() => true);
7806
7618
  const visible = options.visible ?? (() => typeof document === "undefined" || document.visibilityState !== "hidden");
7807
7619
  const state2 = {
7808
7620
  stablePolls: 0,
7809
7621
  consecutiveFailures: 0
7810
7622
  };
7811
- let timer2 = null;
7623
+ let timer = null;
7812
7624
  let stopped = false;
7813
7625
  let inFlight = false;
7814
7626
  const clearTimer = () => {
7815
- if (!timer2) return;
7816
- clearTimeout(timer2);
7817
- timer2 = null;
7627
+ if (!timer) return;
7628
+ clearTimeout(timer);
7629
+ timer = null;
7818
7630
  };
7819
- const schedule2 = () => {
7631
+ const schedule = () => {
7820
7632
  clearTimer();
7821
7633
  if (stopped || !enabled() || !visible()) return;
7822
- timer2 = setTimeout(
7634
+ timer = setTimeout(
7823
7635
  () => void tick(),
7824
7636
  Math.min(
7825
7637
  policy.maxIntervalMs,
@@ -7834,7 +7646,7 @@ function runAdaptivePolling(poll2, policy, options = {}) {
7834
7646
  inFlight = true;
7835
7647
  const startedAt = Date.now();
7836
7648
  try {
7837
- const changed = await poll2();
7649
+ const changed = await poll();
7838
7650
  state2.consecutiveFailures = 0;
7839
7651
  state2.stablePolls = changed === false ? state2.stablePolls + 1 : 0;
7840
7652
  } catch (error) {
@@ -7844,7 +7656,7 @@ function runAdaptivePolling(poll2, policy, options = {}) {
7844
7656
  } finally {
7845
7657
  state2.lastPollDurationMs = Date.now() - startedAt;
7846
7658
  inFlight = false;
7847
- schedule2();
7659
+ schedule();
7848
7660
  }
7849
7661
  };
7850
7662
  const onVisibility = () => {
@@ -7860,7 +7672,7 @@ function runAdaptivePolling(poll2, policy, options = {}) {
7860
7672
  document.addEventListener("visibilitychange", onVisibility);
7861
7673
  }
7862
7674
  if (options.runImmediately ?? true) void tick();
7863
- else schedule2();
7675
+ else schedule();
7864
7676
  return () => {
7865
7677
  stopped = true;
7866
7678
  clearTimer();
@@ -7886,7 +7698,7 @@ import {
7886
7698
  useCallback as useCallback7,
7887
7699
  useRef as useRef7,
7888
7700
  useMemo as useMemo6,
7889
- useSyncExternalStore as useSyncExternalStore9,
7701
+ useSyncExternalStore as useSyncExternalStore8,
7890
7702
  useState as useState13
7891
7703
  } from "react";
7892
7704
 
@@ -7998,7 +7810,7 @@ function ServerStatusProvider({
7998
7810
  storeRef.current = new ServerStatusStore();
7999
7811
  }
8000
7812
  const store = storeRef.current;
8001
- const [version3, setVersion] = useState13(0);
7813
+ const [version4, setVersion] = useState13(0);
8002
7814
  const mountedRef = useRef7(true);
8003
7815
  const refreshInFlightRef = useRef7(null);
8004
7816
  useEffect14(() => store.subscribeAll(() => setVersion((v) => v + 1)), [store]);
@@ -8081,7 +7893,7 @@ function ServerStatusProvider({
8081
7893
  }),
8082
7894
  // version refreshes the statuses/initialLoadComplete snapshots
8083
7895
  // eslint-disable-next-line react-hooks/exhaustive-deps
8084
- [version3, refreshStatuses, getStatus]
7896
+ [version4, refreshStatuses, getStatus]
8085
7897
  );
8086
7898
  return /* @__PURE__ */ jsx8(StatusStoreContext.Provider, { value: store, children: /* @__PURE__ */ jsx8(ServerStatusContext.Provider, { value: contextValue, children }) });
8087
7899
  }
@@ -8103,7 +7915,7 @@ function useServerStatus() {
8103
7915
  }
8104
7916
  function useHostStatus(hostId, statusCheckEnabled = true) {
8105
7917
  const store = useStatusStore();
8106
- const status = useSyncExternalStore9(
7918
+ const status = useSyncExternalStore8(
8107
7919
  (onChange) => store.subscribeHost(hostId, onChange),
8108
7920
  () => store.getStatus(hostId),
8109
7921
  () => store.getStatus(hostId)
@@ -8112,7 +7924,7 @@ function useHostStatus(hostId, statusCheckEnabled = true) {
8112
7924
  }
8113
7925
  function useServerStatusMeta() {
8114
7926
  const store = useStatusStore();
8115
- useSyncExternalStore9(
7927
+ useSyncExternalStore8(
8116
7928
  (onChange) => store.subscribeMeta(onChange),
8117
7929
  () => store.getMetaSnapshot(),
8118
7930
  () => store.getMetaSnapshot()
@@ -8122,7 +7934,7 @@ function useServerStatusMeta() {
8122
7934
  var noStatus = () => "unknown";
8123
7935
  function useOptionalHostStatusEntry(hostId) {
8124
7936
  const store = useContext4(StatusStoreContext);
8125
- const status = useSyncExternalStore9(
7937
+ const status = useSyncExternalStore8(
8126
7938
  (onChange) => store && hostId !== void 0 ? store.subscribeHost(hostId, onChange) : () => {
8127
7939
  },
8128
7940
  () => store && hostId !== void 0 ? store.getStatus(hostId) : "unknown",
@@ -8374,7 +8186,7 @@ var pluginHostBridge = {
8374
8186
  window.removeEventListener(PLUGIN_SETTINGS_CHANGED_EVENT, onChanged);
8375
8187
  };
8376
8188
  }, [pluginId, scope, numericHostId]);
8377
- const save = useCallback8(
8189
+ const save2 = useCallback8(
8378
8190
  async (values) => {
8379
8191
  const saved = await SETTINGS_WRITERS[scope](
8380
8192
  pluginId,
@@ -8385,7 +8197,7 @@ var pluginHostBridge = {
8385
8197
  },
8386
8198
  [pluginId, scope, numericHostId]
8387
8199
  );
8388
- return useMemo8(() => ({ ...state2, save }), [state2, save]);
8200
+ return useMemo8(() => ({ ...state2, save: save2 }), [state2, save2]);
8389
8201
  },
8390
8202
  useHost(hostId) {
8391
8203
  const pluginId = usePluginScope();
@@ -8558,7 +8370,7 @@ function onRemoteServerChange(listener) {
8558
8370
 
8559
8371
  // src/ui/look/layout-registry.ts
8560
8372
  init_define_import_meta_env();
8561
- import { useSyncExternalStore as useSyncExternalStore10 } from "react";
8373
+ import { useSyncExternalStore as useSyncExternalStore9 } from "react";
8562
8374
  var descriptorSnapshot = [];
8563
8375
  var descriptorListeners = /* @__PURE__ */ new Set();
8564
8376
  var registered = createRegistry();
@@ -8584,7 +8396,7 @@ function subscribeDescriptors(listener) {
8584
8396
  };
8585
8397
  }
8586
8398
  function useLayouts() {
8587
- return useSyncExternalStore10(subscribeDescriptors, listLayouts, listLayouts);
8399
+ return useSyncExternalStore9(subscribeDescriptors, listLayouts, listLayouts);
8588
8400
  }
8589
8401
  function layoutLabel(layout, t) {
8590
8402
  if (!layout.pluginId) return layout.label;
@@ -8595,6 +8407,86 @@ function layoutLabel(layout, t) {
8595
8407
 
8596
8408
  // src/ui/plugin-host/app.ts
8597
8409
  init_define_import_meta_env();
8410
+ import { useEffect as useEffect16 } from "react";
8411
+
8412
+ // src/ui/shell/companions.ts
8413
+ init_define_import_meta_env();
8414
+ import { useSyncExternalStore as useSyncExternalStore10 } from "react";
8415
+ var registry5 = createRegistry(byOrderThenId);
8416
+ var registerCompanionDef = registry5.register;
8417
+ var getCompanionDef = registry5.get;
8418
+ var listCompanionDefs = registry5.list;
8419
+ var useCompanionDefs = registry5.useList;
8420
+ var resetCompanions = registry5.reset;
8421
+ function companionApplies(def, tab) {
8422
+ if (!tab.host) return false;
8423
+ if (def.tabTypes?.length && !def.tabTypes.includes(tab.type)) return false;
8424
+ return def.when ? def.when(tab, tab.host) : true;
8425
+ }
8426
+ var STORAGE_KEY = "termix:companions";
8427
+ var EMPTY3 = { openId: null, seen: false };
8428
+ var states = /* @__PURE__ */ new Map();
8429
+ var listeners7 = /* @__PURE__ */ new Set();
8430
+ var version2 = 0;
8431
+ function load() {
8432
+ try {
8433
+ const raw = localStorage.getItem(STORAGE_KEY);
8434
+ if (!raw) return;
8435
+ const parsed = JSON.parse(raw);
8436
+ states = new Map(Object.entries(parsed));
8437
+ } catch {
8438
+ }
8439
+ }
8440
+ function save() {
8441
+ try {
8442
+ const entries = [...states.entries()].slice(-200);
8443
+ localStorage.setItem(
8444
+ STORAGE_KEY,
8445
+ JSON.stringify(Object.fromEntries(entries))
8446
+ );
8447
+ } catch {
8448
+ }
8449
+ }
8450
+ load();
8451
+ function companionKey(tab) {
8452
+ return tab.instanceId ?? tab.id;
8453
+ }
8454
+ function getCompanionState(key) {
8455
+ return states.get(key) ?? EMPTY3;
8456
+ }
8457
+ function patchCompanionState(key, patch) {
8458
+ const next = { ...getCompanionState(key), ...patch };
8459
+ states = new Map(states);
8460
+ states.delete(key);
8461
+ states.set(key, next);
8462
+ version2 += 1;
8463
+ save();
8464
+ for (const listener of listeners7) listener();
8465
+ }
8466
+ function subscribe4(listener) {
8467
+ listeners7.add(listener);
8468
+ return () => listeners7.delete(listener);
8469
+ }
8470
+ function useCompanionState(key) {
8471
+ useSyncExternalStore10(
8472
+ subscribe4,
8473
+ () => version2,
8474
+ () => version2
8475
+ );
8476
+ return getCompanionState(key);
8477
+ }
8478
+ function toggleCompanion(tab, id) {
8479
+ const key = companionKey(tab);
8480
+ const current2 = getCompanionState(key);
8481
+ const target = id ?? current2.openId ?? listCompanionDefs().find((def) => companionApplies(def, tab))?.id ?? null;
8482
+ if (!target) return;
8483
+ patchCompanionState(key, {
8484
+ seen: true,
8485
+ openId: current2.openId === target ? null : target
8486
+ });
8487
+ }
8488
+
8489
+ // src/ui/plugin-host/app.ts
8598
8490
  var ONBOARDING_ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
8599
8491
  function createDocsApi(manifest) {
8600
8492
  const url = pluginDocsUrl(manifest?.docs);
@@ -8689,6 +8581,40 @@ function createPluginApp(pluginId, manifest, contributes, options = {}) {
8689
8581
  })
8690
8582
  );
8691
8583
  },
8584
+ registerCompanion(id, component, options2 = {}) {
8585
+ const useDefaults = options2.useDefaults;
8586
+ const Defaults = useDefaults ? scoped(function CompanionDefaultsReader({
8587
+ onValue
8588
+ }) {
8589
+ const value = useDefaults();
8590
+ const { open, side, size, edgeButton } = value;
8591
+ useEffect16(() => {
8592
+ onValue({ open, side, size, edgeButton });
8593
+ }, [onValue, open, side, size, edgeButton]);
8594
+ return null;
8595
+ }) : void 0;
8596
+ return track(
8597
+ registerCompanionDef({
8598
+ id: `${pluginId}.${id}`,
8599
+ pluginId,
8600
+ component: scoped(
8601
+ component
8602
+ ),
8603
+ icon: withIconBoundary(pluginId, options2.icon),
8604
+ titleKey: options2.titleKey ? key(options2.titleKey) : void 0,
8605
+ order: options2.order,
8606
+ tabTypes: options2.tabTypes,
8607
+ when: options2.when,
8608
+ Defaults
8609
+ })
8610
+ );
8611
+ },
8612
+ toggleCompanion(tab, id) {
8613
+ toggleCompanion(
8614
+ tab,
8615
+ id ? `${pluginId}.${id}` : void 0
8616
+ );
8617
+ },
8692
8618
  registerTab(type, component, options2 = {}) {
8693
8619
  requireDeclared("tab", type, "tab type");
8694
8620
  return track(
@@ -8940,8 +8866,8 @@ function createPluginApp(pluginId, manifest, contributes, options = {}) {
8940
8866
  },
8941
8867
  registerOnboardingStep(step) {
8942
8868
  requireStepId(step.id, "onboarding step");
8943
- const version3 = step.version ?? 1;
8944
- if (!Number.isInteger(version3) || version3 < 1) {
8869
+ const version4 = step.version ?? 1;
8870
+ if (!Number.isInteger(version4) || version4 < 1) {
8945
8871
  throw new Error(
8946
8872
  `${pluginId}: onboarding step "${step.id}" needs a whole version of 1 or more`
8947
8873
  );
@@ -8951,7 +8877,7 @@ function createPluginApp(pluginId, manifest, contributes, options = {}) {
8951
8877
  onboardingSteps.register({
8952
8878
  id: pluginStepKey(pluginId, step.id),
8953
8879
  pluginId,
8954
- version: version3,
8880
+ version: version4,
8955
8881
  titleKey: key(step.titleKey),
8956
8882
  descriptionKey: step.descriptionKey ? key(step.descriptionKey) : void 0,
8957
8883
  icon: step.icon ? withIconBoundary(pluginId, step.icon) : void 0,
@@ -9091,24 +9017,24 @@ function createPluginApp(pluginId, manifest, contributes, options = {}) {
9091
9017
 
9092
9018
  // src/ui/hooks/use-installed-plugins.ts
9093
9019
  init_define_import_meta_env();
9094
- import { useEffect as useEffect16, useState as useState15 } from "react";
9020
+ import { useEffect as useEffect17, useState as useState15 } from "react";
9095
9021
  function useInstalledPlugins() {
9096
9022
  const [plugins, setPlugins] = useState15([]);
9097
- useEffect16(() => {
9023
+ useEffect17(() => {
9098
9024
  let cancelled = false;
9099
9025
  let request = 0;
9100
- const load = () => {
9026
+ const load2 = () => {
9101
9027
  const current2 = ++request;
9102
9028
  void getPlugins().then((loaded) => {
9103
9029
  if (!cancelled && current2 === request) setPlugins(loaded);
9104
9030
  }).catch(() => {
9105
9031
  });
9106
9032
  };
9107
- load();
9108
- window.addEventListener(PLUGINS_CHANGED_EVENT, load);
9033
+ load2();
9034
+ window.addEventListener(PLUGINS_CHANGED_EVENT, load2);
9109
9035
  return () => {
9110
9036
  cancelled = true;
9111
- window.removeEventListener(PLUGINS_CHANGED_EVENT, load);
9037
+ window.removeEventListener(PLUGINS_CHANGED_EVENT, load2);
9112
9038
  };
9113
9039
  }, []);
9114
9040
  return plugins;
@@ -9529,10 +9455,10 @@ function ComponentSlot({
9529
9455
  // src/ui/shell/tab-handles.ts
9530
9456
  init_define_import_meta_env();
9531
9457
  import { useSyncExternalStore as useSyncExternalStore11 } from "react";
9532
- var version2 = 0;
9458
+ var version3 = 0;
9533
9459
  var listeners8 = /* @__PURE__ */ new Set();
9534
9460
  var callbacks = /* @__PURE__ */ new WeakMap();
9535
- function subscribe4(listener) {
9461
+ function subscribe5(listener) {
9536
9462
  listeners8.add(listener);
9537
9463
  return () => listeners8.delete(listener);
9538
9464
  }
@@ -9543,7 +9469,7 @@ function trackTabHandle(ref) {
9543
9469
  callback = (value) => {
9544
9470
  if (ref.current === value) return;
9545
9471
  ref.current = value;
9546
- version2 += 1;
9472
+ version3 += 1;
9547
9473
  for (const listener of listeners8) listener();
9548
9474
  };
9549
9475
  callbacks.set(ref, callback);
@@ -9552,9 +9478,9 @@ function trackTabHandle(ref) {
9552
9478
  }
9553
9479
  function useTabHandlesVersion() {
9554
9480
  return useSyncExternalStore11(
9555
- subscribe4,
9556
- () => version2,
9557
- () => version2
9481
+ subscribe5,
9482
+ () => version3,
9483
+ () => version3
9558
9484
  );
9559
9485
  }
9560
9486
 
@@ -9998,8 +9924,8 @@ function runAdaptiveBackgroundTask(kind, action, task, options = {}) {
9998
9924
 
9999
9925
  // src/ui/shell/tabUtils.tsx
10000
9926
  init_define_import_meta_env();
10001
- import { lazy, memo, Suspense } from "react";
10002
- import { useTranslation as useTranslation8 } from "react-i18next";
9927
+ import { lazy, memo as memo2, Suspense } from "react";
9928
+ import { useTranslation as useTranslation9 } from "react-i18next";
10003
9929
 
10004
9930
  // src/ui/lib/host-to-ssh-host.ts
10005
9931
  init_define_import_meta_env();
@@ -10037,10 +9963,258 @@ function hostToSSHHost(h) {
10037
9963
  };
10038
9964
  }
10039
9965
 
9966
+ // src/ui/shell/CompanionFrame.tsx
9967
+ init_define_import_meta_env();
9968
+ import {
9969
+ memo,
9970
+ useCallback as useCallback9,
9971
+ useEffect as useEffect18,
9972
+ useMemo as useMemo10,
9973
+ useRef as useRef8,
9974
+ useState as useState16
9975
+ } from "react";
9976
+ import { useTranslation as useTranslation8 } from "react-i18next";
9977
+ import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
9978
+ var DEFAULT_SIDE_SIZE = 360;
9979
+ var DEFAULT_BOTTOM_SIZE = 280;
9980
+ var MIN_SIZE = 200;
9981
+ function DefaultsReader({
9982
+ def,
9983
+ onValue
9984
+ }) {
9985
+ const Reader = def.Defaults;
9986
+ const report = useCallback9(
9987
+ (value) => onValue(def.id, value),
9988
+ [def.id, onValue]
9989
+ );
9990
+ return Reader ? /* @__PURE__ */ jsx13(Reader, { onValue: report }) : null;
9991
+ }
9992
+ function sideIcon(side) {
9993
+ if (side === "left") return ArrowLeftToLine;
9994
+ if (side === "bottom") return ArrowDownToLine;
9995
+ return ArrowRightToLine;
9996
+ }
9997
+ var CompanionFrame = memo(function CompanionFrame2({
9998
+ tab,
9999
+ isVisible,
10000
+ children
10001
+ }) {
10002
+ const defs = useCompanionDefs();
10003
+ const applicable = useMemo10(
10004
+ () => defs.filter((def) => companionApplies(def, tab)),
10005
+ // tab.host and tab.type decide it
10006
+ // eslint-disable-next-line react-hooks/exhaustive-deps
10007
+ [defs, tab.type, tab.host]
10008
+ );
10009
+ if (applicable.length === 0) return /* @__PURE__ */ jsx13(Fragment3, { children });
10010
+ return /* @__PURE__ */ jsx13(CompanionLayout, { tab, defs: applicable, isVisible, children });
10011
+ });
10012
+ function CompanionLayout({
10013
+ tab,
10014
+ defs,
10015
+ isVisible,
10016
+ children
10017
+ }) {
10018
+ const { t } = useTranslation8();
10019
+ const isMobile = useIsMobile();
10020
+ const key = companionKey(tab);
10021
+ const state2 = useCompanionState(key);
10022
+ const [defaults3, setDefaults] = useState16(
10023
+ {}
10024
+ );
10025
+ const [dragging, setDragging] = useState16(false);
10026
+ const [mounted, setMounted] = useState16(/* @__PURE__ */ new Set());
10027
+ const rootRef = useRef8(null);
10028
+ const onDefaults = useCallback9((id, value) => {
10029
+ setDefaults((prev) => {
10030
+ const old = prev[id];
10031
+ if (old && old.open === value.open && old.side === value.side && old.size === value.size && old.edgeButton === value.edgeButton) {
10032
+ return prev;
10033
+ }
10034
+ return { ...prev, [id]: value };
10035
+ });
10036
+ }, []);
10037
+ useEffect18(() => {
10038
+ if (state2.seen || !isVisible) return;
10039
+ const ready = defs.every(
10040
+ (def) => !def.Defaults || defaults3[def.id] !== void 0
10041
+ );
10042
+ if (!ready) return;
10043
+ const auto = defs.find((def) => defaults3[def.id]?.open);
10044
+ patchCompanionState(key, { seen: true, openId: auto?.id ?? null });
10045
+ }, [state2.seen, isVisible, defs, defaults3, key]);
10046
+ const active = defs.find((def) => def.id === state2.openId) ?? null;
10047
+ useEffect18(() => {
10048
+ if (!active) return;
10049
+ setMounted(
10050
+ (prev) => prev.has(active.id) ? prev : new Set(prev).add(active.id)
10051
+ );
10052
+ }, [active]);
10053
+ const activeDefaults = active ? defaults3[active.id] : void 0;
10054
+ const side = isMobile ? "bottom" : state2.side ?? activeDefaults?.side ?? "right";
10055
+ const baseSize = state2.size ?? activeDefaults?.size ?? (side === "bottom" ? DEFAULT_BOTTOM_SIZE : DEFAULT_SIDE_SIZE);
10056
+ const setSide = useCallback9(
10057
+ (next) => patchCompanionState(key, { side: next }),
10058
+ [key]
10059
+ );
10060
+ const close = useCallback9(
10061
+ () => patchCompanionState(key, { openId: null }),
10062
+ [key]
10063
+ );
10064
+ const startResize = (event) => {
10065
+ event.preventDefault();
10066
+ const root = rootRef.current;
10067
+ if (!root) return;
10068
+ const rect = root.getBoundingClientRect();
10069
+ const total = side === "bottom" ? rect.height : rect.width;
10070
+ const max = Math.max(MIN_SIZE, total - MIN_SIZE);
10071
+ setDragging(true);
10072
+ const move = (e) => {
10073
+ const raw = side === "right" ? rect.right - e.clientX : side === "left" ? e.clientX - rect.left : rect.bottom - e.clientY;
10074
+ patchCompanionState(key, {
10075
+ size: Math.round(Math.min(max, Math.max(MIN_SIZE, raw)))
10076
+ });
10077
+ };
10078
+ const up = () => {
10079
+ setDragging(false);
10080
+ window.removeEventListener("mousemove", move);
10081
+ window.removeEventListener("mouseup", up);
10082
+ };
10083
+ window.addEventListener("mousemove", move);
10084
+ window.addEventListener("mouseup", up);
10085
+ };
10086
+ const getHandle = useCallback9(
10087
+ () => tab.terminalRef?.current ?? null,
10088
+ [tab.terminalRef]
10089
+ );
10090
+ const sshHost = useMemo10(
10091
+ () => tab.host ? hostToSSHHost(tab.host) : void 0,
10092
+ [tab.host]
10093
+ );
10094
+ const showEdge = defs.filter((def) => defaults3[def.id]?.edgeButton !== false);
10095
+ const vertical = side === "bottom";
10096
+ const clamped = vertical ? Math.min(baseSize, 2e3) : baseSize;
10097
+ const paneStyle = isMobile ? { height: "55%" } : vertical ? { height: clamped } : { width: clamped };
10098
+ const SideIcon = sideIcon(side);
10099
+ return /* @__PURE__ */ jsxs6("div", { className: "flex h-full w-full min-h-0 min-w-0", children: [
10100
+ defs.map(
10101
+ (def) => def.Defaults ? /* @__PURE__ */ jsx13(DefaultsReader, { def, onValue: onDefaults }, def.id) : null
10102
+ ),
10103
+ /* @__PURE__ */ jsxs6(
10104
+ "div",
10105
+ {
10106
+ ref: rootRef,
10107
+ className: cn(
10108
+ "relative flex flex-1 min-h-0 min-w-0",
10109
+ vertical ? "flex-col" : side === "left" ? "flex-row-reverse" : "flex-row"
10110
+ ),
10111
+ children: [
10112
+ /* @__PURE__ */ jsx13("div", { className: "relative flex-1 min-h-0 min-w-0", children }),
10113
+ defs.map((def) => {
10114
+ if (!mounted.has(def.id)) return null;
10115
+ const open = active?.id === def.id;
10116
+ const Component = def.component;
10117
+ const Icon = def.icon;
10118
+ return /* @__PURE__ */ jsxs6(
10119
+ "div",
10120
+ {
10121
+ className: cn(
10122
+ "relative flex-shrink-0 flex-col min-h-0 min-w-0 bg-background border-border",
10123
+ open ? "flex" : "hidden",
10124
+ vertical ? "border-t" : side === "left" ? "border-r" : "border-l",
10125
+ isMobile && "absolute inset-x-0 bottom-0 z-20 shadow-lg"
10126
+ ),
10127
+ style: paneStyle,
10128
+ children: [
10129
+ !isMobile && /* @__PURE__ */ jsx13(
10130
+ "div",
10131
+ {
10132
+ role: "separator",
10133
+ "aria-label": t("companion.resize"),
10134
+ onMouseDown: startResize,
10135
+ className: cn(
10136
+ "absolute z-10",
10137
+ vertical ? "inset-x-0 -top-1 h-2 cursor-row-resize" : side === "left" ? "inset-y-0 -right-1 w-2 cursor-col-resize" : "inset-y-0 -left-1 w-2 cursor-col-resize",
10138
+ dragging && "bg-primary/30"
10139
+ )
10140
+ }
10141
+ ),
10142
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1.5 h-8 px-2 border-b border-border flex-shrink-0", children: [
10143
+ Icon && /* @__PURE__ */ jsx13(Icon, { className: "size-3.5 text-muted-foreground" }),
10144
+ /* @__PURE__ */ jsx13("span", { className: "text-xs font-medium truncate flex-1", children: def.titleKey ? t(def.titleKey) : def.id }),
10145
+ !isMobile && /* @__PURE__ */ jsx13(
10146
+ "button",
10147
+ {
10148
+ type: "button",
10149
+ className: "p-1 text-muted-foreground hover:text-foreground",
10150
+ title: t("companion.moveSide"),
10151
+ onClick: () => setSide(
10152
+ side === "right" ? "bottom" : side === "bottom" ? "left" : "right"
10153
+ ),
10154
+ children: /* @__PURE__ */ jsx13(SideIcon, { className: "size-3.5" })
10155
+ }
10156
+ ),
10157
+ /* @__PURE__ */ jsx13(
10158
+ "button",
10159
+ {
10160
+ type: "button",
10161
+ className: "p-1 text-muted-foreground hover:text-foreground",
10162
+ title: t("companion.close"),
10163
+ onClick: close,
10164
+ children: /* @__PURE__ */ jsx13(X, { className: "size-3.5" })
10165
+ }
10166
+ )
10167
+ ] }),
10168
+ /* @__PURE__ */ jsx13("div", { className: "flex-1 min-h-0 min-w-0 overflow-hidden", children: /* @__PURE__ */ jsx13(
10169
+ Component,
10170
+ {
10171
+ tab,
10172
+ host: tab.host,
10173
+ sshHost,
10174
+ getHandle,
10175
+ side,
10176
+ isVisible: isVisible && open,
10177
+ close,
10178
+ setSide
10179
+ }
10180
+ ) })
10181
+ ]
10182
+ },
10183
+ def.id
10184
+ );
10185
+ })
10186
+ ]
10187
+ }
10188
+ ),
10189
+ showEdge.length > 0 && !isMobile && /* @__PURE__ */ jsx13("div", { className: "flex flex-col flex-shrink-0 w-6 items-center gap-1 pt-1 border-l border-border bg-background", children: showEdge.map((def) => {
10190
+ const Icon = def.icon ?? PanelRight;
10191
+ const open = active?.id === def.id;
10192
+ return /* @__PURE__ */ jsx13(
10193
+ "button",
10194
+ {
10195
+ type: "button",
10196
+ title: def.titleKey ? t(def.titleKey) : def.id,
10197
+ "aria-pressed": open,
10198
+ onClick: () => patchCompanionState(key, {
10199
+ seen: true,
10200
+ openId: open ? null : def.id
10201
+ }),
10202
+ className: cn(
10203
+ "p-1 text-muted-foreground hover:text-foreground",
10204
+ open && "text-foreground bg-accent"
10205
+ ),
10206
+ children: /* @__PURE__ */ jsx13(Icon, { className: "size-3.5" })
10207
+ },
10208
+ def.id
10209
+ );
10210
+ }) })
10211
+ ] });
10212
+ }
10213
+
10040
10214
  // src/ui/shell/tabUtils.tsx
10041
- import { jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
10215
+ import { jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
10042
10216
  var PluginsTab = lazy(
10043
- () => import("./PluginsTab-OXQQ7M4I.js").then((m) => ({ default: m.PluginsTab }))
10217
+ () => import("./PluginsTab-YAGYRBIE.js").then((m) => ({ default: m.PluginsTab }))
10044
10218
  );
10045
10219
  function preloadTabSurface(type) {
10046
10220
  const loader = getTabType(type)?.preload;
@@ -10053,37 +10227,37 @@ function EmptyState({
10053
10227
  icon: Icon,
10054
10228
  messageKey
10055
10229
  }) {
10056
- const { t } = useTranslation8();
10057
- return /* @__PURE__ */ jsxs6("div", { className: "flex flex-col items-center justify-center flex-1 gap-3 p-6 text-center", children: [
10058
- /* @__PURE__ */ jsx13("div", { className: "size-10 rounded-full bg-muted/40 flex items-center justify-center", children: /* @__PURE__ */ jsx13(Icon, { className: "size-5 text-muted-foreground/30" }) }),
10059
- /* @__PURE__ */ jsx13("span", { className: "text-sm font-semibold text-muted-foreground/60", children: t(messageKey) })
10230
+ const { t } = useTranslation9();
10231
+ return /* @__PURE__ */ jsxs7("div", { className: "flex flex-col items-center justify-center flex-1 gap-3 p-6 text-center", children: [
10232
+ /* @__PURE__ */ jsx14("div", { className: "size-10 rounded-full bg-muted/40 flex items-center justify-center", children: /* @__PURE__ */ jsx14(Icon, { className: "size-5 text-muted-foreground/30" }) }),
10233
+ /* @__PURE__ */ jsx14("span", { className: "text-sm font-semibold text-muted-foreground/60", children: t(messageKey) })
10060
10234
  ] });
10061
10235
  }
10062
10236
  function TabChunkFallback() {
10063
- return /* @__PURE__ */ jsx13("div", { className: "flex h-full w-full items-center justify-center bg-background", children: /* @__PURE__ */ jsx13("div", { className: "size-5 rounded-full border-2 border-muted-foreground/30 border-t-muted-foreground/70 animate-spin" }) });
10237
+ return /* @__PURE__ */ jsx14("div", { className: "flex h-full w-full items-center justify-center bg-background", children: /* @__PURE__ */ jsx14("div", { className: "size-5 rounded-full border-2 border-muted-foreground/30 border-t-muted-foreground/70 animate-spin" }) });
10064
10238
  }
10065
10239
  function withTabSuspense(node) {
10066
- return /* @__PURE__ */ jsx13(Suspense, { fallback: /* @__PURE__ */ jsx13(TabChunkFallback, {}), children: node });
10240
+ return /* @__PURE__ */ jsx14(Suspense, { fallback: /* @__PURE__ */ jsx14(TabChunkFallback, {}), children: node });
10067
10241
  }
10068
10242
  function PanelTabFrame({ children }) {
10069
- return /* @__PURE__ */ jsx13("div", { className: "flex h-full w-full justify-center overflow-y-auto bg-background", children: /* @__PURE__ */ jsx13("div", { className: "flex flex-col flex-1 min-h-0 w-full max-w-5xl", children }) });
10243
+ return /* @__PURE__ */ jsx14("div", { className: "flex h-full w-full justify-center overflow-y-auto bg-background", children: /* @__PURE__ */ jsx14("div", { className: "flex flex-col flex-1 min-h-0 w-full max-w-5xl", children }) });
10070
10244
  }
10071
10245
  function tabIcon(type) {
10072
10246
  switch (type) {
10073
10247
  case "user-profile":
10074
10248
  case "admin-settings":
10075
- return /* @__PURE__ */ jsx13(Settings, { className: "size-3.5" });
10249
+ return /* @__PURE__ */ jsx14(Settings, { className: "size-3.5" });
10076
10250
  case "split-screen":
10077
- return /* @__PURE__ */ jsx13(LayoutPanelLeft, { className: "size-3.5" });
10251
+ return /* @__PURE__ */ jsx14(LayoutPanelLeft, { className: "size-3.5" });
10078
10252
  case "plugins":
10079
- return /* @__PURE__ */ jsx13(Puzzle, { className: "size-3.5" });
10253
+ return /* @__PURE__ */ jsx14(Puzzle, { className: "size-3.5" });
10080
10254
  default: {
10081
10255
  const Icon = getTabType(type)?.icon;
10082
- return Icon ? /* @__PURE__ */ jsx13(Icon, { className: "size-3.5" }) : null;
10256
+ return Icon ? /* @__PURE__ */ jsx14(Icon, { className: "size-3.5" }) : null;
10083
10257
  }
10084
10258
  }
10085
10259
  }
10086
- var RegisteredTab = memo(function RegisteredTab2({
10260
+ var RegisteredTab = memo2(function RegisteredTab2({
10087
10261
  def,
10088
10262
  tab,
10089
10263
  isVisible,
@@ -10093,7 +10267,7 @@ var RegisteredTab = memo(function RegisteredTab2({
10093
10267
  }) {
10094
10268
  const { host, label } = tab;
10095
10269
  if (def.requiresHost && !host) {
10096
- return /* @__PURE__ */ jsx13(
10270
+ return /* @__PURE__ */ jsx14(
10097
10271
  EmptyState,
10098
10272
  {
10099
10273
  icon: Server,
@@ -10102,7 +10276,7 @@ var RegisteredTab = memo(function RegisteredTab2({
10102
10276
  );
10103
10277
  }
10104
10278
  const Component = def.component;
10105
- const content = /* @__PURE__ */ jsx13(
10279
+ const content = /* @__PURE__ */ jsx14(
10106
10280
  Component,
10107
10281
  {
10108
10282
  tab,
@@ -10119,10 +10293,10 @@ var RegisteredTab = memo(function RegisteredTab2({
10119
10293
  }
10120
10294
  );
10121
10295
  return withTabSuspense(
10122
- def.panelFrame ? /* @__PURE__ */ jsx13(PanelTabFrame, { children: content }) : content
10296
+ /* @__PURE__ */ jsx14(CompanionFrame, { tab, isVisible, children: def.panelFrame ? /* @__PURE__ */ jsx14(PanelTabFrame, { children: content }) : content })
10123
10297
  );
10124
10298
  });
10125
- var PanelAsTab = memo(function PanelAsTab2({
10299
+ var PanelAsTab = memo2(function PanelAsTab2({
10126
10300
  panel,
10127
10301
  isVisible,
10128
10302
  targetTab,
@@ -10130,7 +10304,7 @@ var PanelAsTab = memo(function PanelAsTab2({
10130
10304
  }) {
10131
10305
  const Panel = panel.component;
10132
10306
  return withTabSuspense(
10133
- /* @__PURE__ */ jsx13(PanelTabFrame, { children: /* @__PURE__ */ jsx13(
10307
+ /* @__PURE__ */ jsx14(PanelTabFrame, { children: /* @__PURE__ */ jsx14(
10134
10308
  Panel,
10135
10309
  {
10136
10310
  targetTab,
@@ -10155,7 +10329,7 @@ function renderTabContent(tab, context) {
10155
10329
  case "split-screen":
10156
10330
  return null;
10157
10331
  case "plugins":
10158
- return withTabSuspense(/* @__PURE__ */ jsx13(PluginsTab, {}));
10332
+ return withTabSuspense(/* @__PURE__ */ jsx14(PluginsTab, {}));
10159
10333
  case "user-profile":
10160
10334
  case "admin-settings":
10161
10335
  return null;
@@ -10165,7 +10339,7 @@ function renderTabContent(tab, context) {
10165
10339
  if (!def) {
10166
10340
  const panel = getPanel(tab.type);
10167
10341
  if (panel) {
10168
- return /* @__PURE__ */ jsx13(
10342
+ return /* @__PURE__ */ jsx14(
10169
10343
  PanelAsTab,
10170
10344
  {
10171
10345
  panel,
@@ -10175,9 +10349,9 @@ function renderTabContent(tab, context) {
10175
10349
  }
10176
10350
  );
10177
10351
  }
10178
- return /* @__PURE__ */ jsx13(PluginViewPlaceholder, { kind: "tab", viewId: tab.type });
10352
+ return /* @__PURE__ */ jsx14(PluginViewPlaceholder, { kind: "tab", viewId: tab.type });
10179
10353
  }
10180
- return /* @__PURE__ */ jsx13(
10354
+ return /* @__PURE__ */ jsx14(
10181
10355
  RegisteredTab,
10182
10356
  {
10183
10357
  def,
@@ -10225,6 +10399,7 @@ export {
10225
10399
  remScale,
10226
10400
  useIsMobile,
10227
10401
  getLoginMethods,
10402
+ getRememberDeviceAllowed,
10228
10403
  submitLoginMethod,
10229
10404
  startLoginRedirect,
10230
10405
  verifySecondFactor,
@@ -10244,20 +10419,6 @@ export {
10244
10419
  changeAppLanguage,
10245
10420
  rememberLoginLanguage,
10246
10421
  i18n_default,
10247
- probeServer,
10248
- getLinkPreview,
10249
- completeLink,
10250
- reloginLink,
10251
- unlinkServer,
10252
- syncNow,
10253
- updateSyncSettings,
10254
- getSyncConflicts,
10255
- settleSyncConflict,
10256
- getSyncErrors,
10257
- retrySyncErrors,
10258
- setSyncStatus,
10259
- refreshSyncStatus,
10260
- useSyncStatus,
10261
10422
  getPanel,
10262
10423
  listPanels,
10263
10424
  usePanels,