@xpufx/paseo-top 0.4.3 → 0.4.5

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 (130) hide show
  1. package/OVERVIEW.md +17 -0
  2. package/client/host-ui.tsx +184 -0
  3. package/client/multi-host-view.tsx +57 -43
  4. package/client/multi-host.ts +36 -13
  5. package/client/pill-labels.ts +124 -7
  6. package/client/pill.tsx +724 -669
  7. package/client/resources-query.ts +1 -1
  8. package/client/settings-events.ts +33 -0
  9. package/client/settings-ui.tsx +86 -9
  10. package/client/surface.tsx +310 -274
  11. package/client/telemetry-copy.ts +8 -2
  12. package/client/telemetry.tsx +372 -321
  13. package/client/turn-panel.tsx +126 -104
  14. package/client/vendor/paseo-plugin-helper/{host.ts → core/host.ts} +21 -61
  15. package/client/vendor/paseo-plugin-helper/core/index.ts +76 -0
  16. package/client/vendor/paseo-plugin-helper/core/plugin-load-count.ts +89 -0
  17. package/client/vendor/paseo-plugin-helper/{query-refresh.ts → core/query-refresh.ts} +2 -2
  18. package/client/vendor/paseo-plugin-helper/{query.ts → core/query.ts} +2 -2
  19. package/client/vendor/paseo-plugin-helper/{settings.ts → core/settings.ts} +1 -1
  20. package/client/vendor/paseo-plugin-helper/{shared-settings.ts → core/shared-settings.ts} +2 -2
  21. package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/clipboard.ts +2 -2
  22. package/client/vendor/paseo-plugin-helper/lifecycle/flair.ts +26 -0
  23. package/client/vendor/paseo-plugin-helper/{theme/color-utils.ts → lifecycle/host-color.ts} +95 -38
  24. package/client/vendor/paseo-plugin-helper/lifecycle/host-theme.tsx +109 -0
  25. package/client/vendor/paseo-plugin-helper/lifecycle/index.ts +23 -0
  26. package/client/vendor/paseo-plugin-helper/lifecycle/multi-host.ts +66 -0
  27. package/client/vendor/paseo-plugin-helper/{panel.tsx → lifecycle/panel.tsx} +7 -7
  28. package/client/vendor/paseo-plugin-helper/{pill.tsx → lifecycle/pill.tsx} +118 -338
  29. package/client/vendor/paseo-plugin-helper/lifecycle/providers.tsx +46 -0
  30. package/client/vendor/paseo-plugin-helper/lifecycle/scroll-owner.ts +26 -0
  31. package/client/vendor/paseo-plugin-helper/lifecycle/surface.tsx +140 -0
  32. package/client/vendor/paseo-plugin-helper/ui/color.ts +1 -0
  33. package/client/vendor/paseo-plugin-helper/ui/content.tsx +1326 -0
  34. package/client/vendor/paseo-plugin-helper/ui/controls.tsx +1018 -0
  35. package/client/vendor/paseo-plugin-helper/{components/DataTable.tsx → ui/data.tsx} +55 -47
  36. package/client/vendor/paseo-plugin-helper/ui/index.ts +44 -0
  37. package/client/vendor/paseo-plugin-helper/ui/layout.tsx +263 -0
  38. package/client/vendor/paseo-plugin-helper/ui/modal.tsx +111 -0
  39. package/client/vendor/paseo-plugin-helper/ui/plugin-reload-banner.tsx +119 -0
  40. package/client/vendor/paseo-plugin-helper/{settings-screen.tsx → ui/settings-screen.tsx} +3 -3
  41. package/client/vendor/paseo-plugin-helper/ui/settings.ts +36 -0
  42. package/client/vendor/paseo-plugin-helper/ui/text.tsx +821 -0
  43. package/client/vendor/paseo-plugin-helper/ui/theme.tsx +1 -0
  44. package/client/vendor/permission-audit/client.tsx +706 -0
  45. package/client/vendor/permission-audit/index.tsx +1 -0
  46. package/index.client.tsx +58 -37
  47. package/index.server.ts +256 -227
  48. package/package.json +14 -2
  49. package/paseo-plugin.json +2 -0
  50. package/screenshots/1-paseo-top-modal-system.jpg +0 -0
  51. package/screenshots/2-paseo-top-modal-workspace.jpg +0 -0
  52. package/screenshots/3-paseo-top-modal-settings.jpg +0 -0
  53. package/screenshots/4-paseo-top-modal-about.jpg +0 -0
  54. package/screenshots/paseo-top-dashboard.jpg +0 -0
  55. package/screenshots/paseo-top-general-view.jpg +0 -0
  56. package/server/resources.ts +98 -0
  57. package/server/vendor/paseo-plugin-helper/custom-pills.ts +70 -0
  58. package/server/vendor/paseo-plugin-helper/index.ts +4 -0
  59. package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
  60. package/server/vendor/paseo-plugin-helper/mcp-injection.ts +20 -2
  61. package/server/vendor/paseo-plugin-helper/plugins.ts +184 -19
  62. package/server/vendor/paseo-plugin-helper/rpc-guard.ts +2 -2
  63. package/server/vendor/paseo-plugin-helper/storage-guard.ts +294 -0
  64. package/server/vendor/paseo-plugin-helper/storage.ts +36 -2
  65. package/server/vendor/paseo-plugin-helper/telemetry.ts +486 -0
  66. package/server/vendor/paseo-plugin-helper/tickets.ts +1232 -0
  67. package/server/vendor/permission-audit/index.ts +1 -0
  68. package/server/vendor/permission-audit/server.ts +839 -0
  69. package/shared/helper-version.ts +20 -0
  70. package/shared/multi-host.ts +156 -155
  71. package/shared/resources.ts +146 -1
  72. package/shared/vendor/paseo-plugin-helper/async.ts +14 -7
  73. package/shared/vendor/paseo-plugin-helper/features.ts +244 -0
  74. package/shared/vendor/paseo-plugin-helper/forge.ts +27 -0
  75. package/shared/vendor/paseo-plugin-helper/index.ts +2 -0
  76. package/shared/vendor/paseo-plugin-helper/tickets.ts +1104 -0
  77. package/shared/vendor/permission-audit/filter.ts +61 -0
  78. package/shared/vendor/permission-audit/index.ts +2 -0
  79. package/shared/vendor/permission-audit/shared.ts +119 -0
  80. package/shared/version.ts +1 -1
  81. package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +0 -493
  82. package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +0 -250
  83. package/client/vendor/paseo-plugin-helper/components/Badge.tsx +0 -156
  84. package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -184
  85. package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -225
  86. package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +0 -196
  87. package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +0 -277
  88. package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +0 -172
  89. package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +0 -180
  90. package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +0 -97
  91. package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +0 -70
  92. package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +0 -75
  93. package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +0 -446
  94. package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +0 -247
  95. package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +0 -117
  96. package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +0 -53
  97. package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +0 -118
  98. package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -80
  99. package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -215
  100. package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +0 -80
  101. package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +0 -319
  102. package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +0 -150
  103. package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +0 -163
  104. package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +0 -157
  105. package/client/vendor/paseo-plugin-helper/components/index.ts +0 -25
  106. package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -224
  107. package/client/vendor/paseo-plugin-helper/forge-icon.tsx +0 -79
  108. package/client/vendor/paseo-plugin-helper/icon.tsx +0 -39
  109. package/client/vendor/paseo-plugin-helper/index.ts +0 -29
  110. package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +0 -49
  111. package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +0 -103
  112. package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +0 -65
  113. package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +0 -378
  114. package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +0 -51
  115. package/client/vendor/paseo-plugin-helper/layout/Row.tsx +0 -39
  116. package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +0 -39
  117. package/client/vendor/paseo-plugin-helper/layout/index.ts +0 -7
  118. package/client/vendor/paseo-plugin-helper/styles/index.ts +0 -1
  119. package/client/vendor/paseo-plugin-helper/styles/recipes.ts +0 -550
  120. package/client/vendor/paseo-plugin-helper/surface.tsx +0 -80
  121. package/client/vendor/paseo-plugin-helper/theme/flair.ts +0 -76
  122. package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +0 -121
  123. package/client/vendor/paseo-plugin-helper/theme/index.ts +0 -7
  124. package/client/vendor/paseo-plugin-helper/theme/provider.tsx +0 -178
  125. package/client/vendor/paseo-plugin-helper/theme/responsive.ts +0 -213
  126. package/client/vendor/paseo-plugin-helper/theme/tokens.ts +0 -200
  127. package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +0 -57
  128. /package/client/vendor/paseo-plugin-helper/{command-center.ts → core/command-center.ts} +0 -0
  129. /package/client/vendor/paseo-plugin-helper/{snapshot.ts → core/snapshot.ts} +0 -0
  130. /package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/haptics.ts +0 -0
@@ -1,7 +1,63 @@
1
1
  import type { StatusVariant, ThemeColors } from "../../../../shared/vendor/paseo-plugin-helper/types";
2
2
 
3
3
  /**
4
- * Converts a hex color and opacity (0.0 to 1.0) into an 8-character hex or rgba string.
4
+ * The single host-color fallback owned by the helper.
5
+ *
6
+ * A host `theme` prop normally supplies every token, but a partial or older
7
+ * payload may omit some. Rather than each plugin inventing its own
8
+ * `colors.border || "#334155"` guard, the helper resolves a theme once, here,
9
+ * and every adapter reads the resolved token. Plugins may pass this palette to
10
+ * code that renders outside a provider (recipes, previews, tests); they must
11
+ * not define their own literal fallbacks.
12
+ */
13
+ export const FALLBACK_COLORS: ThemeColors = {
14
+ surface0: "#18181b",
15
+ surface1: "#27272a",
16
+ surface2: "#3f3f46",
17
+ border: "#3f3f46",
18
+ foreground: "#fafafa",
19
+ foregroundMuted: "#a1a1aa",
20
+ accent: "#3b82f6",
21
+ accentForeground: "#ffffff",
22
+ statusSuccess: "#22c55e",
23
+ statusWarning: "#eab308",
24
+ statusDanger: "#ef4444",
25
+ };
26
+
27
+ /**
28
+ * The host theme carries no shadow token, so shadows use this documented
29
+ * overlay constant owned by the helper. It is not a `ThemeColors` token and is
30
+ * intentionally not part of the palette a render guard checks; it is the one
31
+ * allowed non-theme color.
32
+ */
33
+ export const HOST_SHADOW_COLOR = "rgba(0, 0, 0, 0.4)";
34
+
35
+ /**
36
+ * Fills a possibly-partial host `theme.colors` from {@link FALLBACK_COLORS}.
37
+ * This is the one place a missing token becomes a literal, so surfaces never
38
+ * need a per-property `|| "#hex"` guard.
39
+ */
40
+ export function resolveHostColors(colors?: Partial<ThemeColors> | null): ThemeColors {
41
+ return { ...FALLBACK_COLORS, ...(colors ?? {}) };
42
+ }
43
+
44
+ /**
45
+ * Paseo Plugin Helper — UI color tokens (`paseo-plugin-helper/ui`).
46
+ *
47
+ * Pure color math for the ui/ adapter layer. These helpers are deliberately
48
+ * self-contained: they never read the DOM, never scrape CSS variables, and
49
+ * never import the frozen `client/` theme system. Colors arrive from the host
50
+ * `theme` prop via {@link HostThemeProvider} and are combined here.
51
+ */
52
+
53
+ /**
54
+ * Converts a hex color and opacity (0.0 to 1.0) into an 8-character hex or
55
+ * rgba string.
56
+ *
57
+ * - `#rgb` / `#rrggbb` / `#rrggbbaa` → `#rrggbbaa` (alpha channel replaced)
58
+ * - `rgb()` / `rgba()` → `rgba(r, g, b, opacity)`
59
+ * - anything else is returned unchanged (named colors, `transparent`, …)
60
+ * - empty/invalid color degrades to `rgba(0, 0, 0, opacity)`
5
61
  */
6
62
  export function alpha(color: string, opacity: number): string {
7
63
  if (!color) return `rgba(0, 0, 0, ${opacity})`;
@@ -39,42 +95,6 @@ export function alpha(color: string, opacity: number): string {
39
95
  return color;
40
96
  }
41
97
 
42
- /**
43
- * Calculates relative luminance (WCAG 2.0 formula) from a hex color.
44
- */
45
- export function getLuminance(hexColor: string): number {
46
- let hex = hexColor.replace("#", "");
47
- if (hex.length === 3) {
48
- hex = hex
49
- .split("")
50
- .map((c) => c + c)
51
- .join("");
52
- }
53
- const r = parseInt(hex.slice(0, 2), 16) / 255;
54
- const g = parseInt(hex.slice(2, 4), 16) / 255;
55
- const b = parseInt(hex.slice(4, 6), 16) / 255;
56
-
57
- const toLinear = (c: number) => (c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
58
-
59
- return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
60
- }
61
-
62
- /**
63
- * Returns either lightText or darkText depending on which has highest contrast against bgHex.
64
- */
65
- export function getContrastColor(
66
- bgHex: string,
67
- lightText = "#ffffff",
68
- darkText = "#0f172a",
69
- ): string {
70
- try {
71
- const lum = getLuminance(bgHex);
72
- return lum > 0.45 ? darkText : lightText;
73
- } catch {
74
- return lightText;
75
- }
76
- }
77
-
78
98
  /**
79
99
  * Resolves a StatusVariant to a corresponding theme color hex string.
80
100
  */
@@ -102,7 +122,8 @@ export function getStatusColor(
102
122
  }
103
123
 
104
124
  /**
105
- * Returns background, text, and border styling colors for a given status variant.
125
+ * Returns background, text, and border styling colors for a given status
126
+ * variant, derived from the host theme colors via {@link alpha}.
106
127
  */
107
128
  export function getVariantPalette(
108
129
  variant: StatusVariant,
@@ -116,3 +137,39 @@ export function getVariantPalette(
116
137
  border: alpha(base, 0.3),
117
138
  };
118
139
  }
140
+
141
+ /**
142
+ * Calculates relative luminance (WCAG 2.0 formula) from a hex color.
143
+ */
144
+ export function getLuminance(hexColor: string): number {
145
+ let hex = hexColor.replace("#", "");
146
+ if (hex.length === 3) {
147
+ hex = hex
148
+ .split("")
149
+ .map((c) => c + c)
150
+ .join("");
151
+ }
152
+ const r = parseInt(hex.slice(0, 2), 16) / 255;
153
+ const g = parseInt(hex.slice(2, 4), 16) / 255;
154
+ const b = parseInt(hex.slice(4, 6), 16) / 255;
155
+
156
+ const toLinear = (c: number) => (c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
157
+
158
+ return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
159
+ }
160
+
161
+ /**
162
+ * Returns either lightText or darkText depending on which has highest contrast against bgHex.
163
+ */
164
+ export function getContrastColor(
165
+ bgHex: string,
166
+ lightText = "#ffffff",
167
+ darkText = "#0f172a",
168
+ ): string {
169
+ try {
170
+ const lum = getLuminance(bgHex);
171
+ return lum > 0.45 ? darkText : lightText;
172
+ } catch {
173
+ return lightText;
174
+ }
175
+ }
@@ -0,0 +1,109 @@
1
+ import React, { createContext, useContext, type ReactNode } from "react";
2
+ import type { PluginTheme, ResponsiveLayout, StatusVariant, ThemeColors } from "../../../../shared/vendor/paseo-plugin-helper/types";
3
+ import { alpha, getStatusColor, getVariantPalette, FALLBACK_COLORS, resolveHostColors } from "./host-color";
4
+
5
+ /**
6
+ * Paseo Plugin Helper — Host theme context (`paseo-plugin-helper/ui`).
7
+ *
8
+ * The ui/ adapter layer's ONLY source of color tokens. The host hands every
9
+ * surface a `theme` prop (`PluginTheme`); `HostThemeProvider` carries those
10
+ * colors to every ui/ adapter below it.
11
+ *
12
+ * Contract — what this context deliberately does NOT do:
13
+ * - No DOM CSS variable scraping. Colors come from the host `theme` prop only;
14
+ * the removed legacy theme provider's `getComputedStyle` scraper is gone.
15
+ * - No flair, density, typography, or padding machinery. The host owns the
16
+ * design language; ui/ only consumes its color tokens.
17
+ * - No scroll-ownership state machine. See `ui/modal.tsx`.
18
+ *
19
+ * Usage:
20
+ * ```tsx
21
+ * <HostThemeProvider theme={props.theme}>
22
+ * <HostCard>…</HostCard>
23
+ * </HostThemeProvider>
24
+ * ```
25
+ */
26
+
27
+ export interface HostTheme {
28
+ /** Host theme colors, straight from the host `theme` prop. */
29
+ colors: ThemeColors;
30
+ /** Color-token opacity helper bound to nothing but its arguments. */
31
+ alpha: (color: string, opacity: number) => string;
32
+ /** Status variant → host theme color. */
33
+ getStatusColor: (variant: StatusVariant) => string;
34
+ /** Status variant → { bg, text, border } palette derived via `alpha`. */
35
+ getVariantPalette: (variant: StatusVariant) => { bg: string; text: string; border: string };
36
+ }
37
+
38
+ /**
39
+ * Neutral fallback palette so ui/ adapters still render outside a provider
40
+ * (tests, previews, host surfaces that have not migrated yet). It is the single
41
+ * helper-owned fallback; see {@link FALLBACK_COLORS}. A supplied `theme` is
42
+ * merged over it, so a partial host payload never paints a raw literal.
43
+ */
44
+ function createHostTheme(colors: ThemeColors): HostTheme {
45
+ return {
46
+ colors,
47
+ alpha,
48
+ getStatusColor: (variant) => getStatusColor(variant, colors),
49
+ getVariantPalette: (variant) => getVariantPalette(variant, colors),
50
+ };
51
+ }
52
+
53
+ const FALLBACK_HOST_THEME: HostTheme = createHostTheme(FALLBACK_COLORS);
54
+
55
+ const HostThemeContext = createContext<HostTheme>(FALLBACK_HOST_THEME);
56
+
57
+ export interface HostThemeProviderProps {
58
+ /** The host `theme` prop for this surface — the single source of colors. */
59
+ theme: PluginTheme;
60
+ children: ReactNode;
61
+ }
62
+
63
+ /**
64
+ * Provides host theme colors to every ui/ adapter in the subtree. Same `theme`
65
+ * prop shape the removed legacy `PluginThemeProvider` took, but no scraped
66
+ * variables and no flair.
67
+ */
68
+ export function HostThemeProvider({ theme, children }: HostThemeProviderProps) {
69
+ const value = createHostTheme(resolveHostColors(theme.colors));
70
+ return <HostThemeContext.Provider value={value}>{children}</HostThemeContext.Provider>;
71
+ }
72
+
73
+ /**
74
+ * Reads the host theme provided by {@link HostThemeProvider}. Falls back to a
75
+ * static neutral palette when no provider is above — adapters never throw and
76
+ * never scrape.
77
+ */
78
+ export function useHostTheme(): HostTheme {
79
+ return useContext(HostThemeContext);
80
+ }
81
+
82
+ // --- Host layout -----------------------------------------------------------
83
+
84
+ const DEFAULT_LAYOUT: ResponsiveLayout = {
85
+ compact: false,
86
+ platform: "web",
87
+ };
88
+
89
+ const HostLayoutContext = createContext<ResponsiveLayout>(DEFAULT_LAYOUT);
90
+
91
+ export interface HostLayoutProviderProps {
92
+ /** The host `layout` prop for this surface. */
93
+ layout: ResponsiveLayout;
94
+ children: ReactNode;
95
+ }
96
+
97
+ /**
98
+ * Provides the host layout descriptor (compact / platform / width) to ui/
99
+ * adapters that branch on it (e.g. `HostResponsive`). Optional: adapters
100
+ * fall back to a wide-web default when no provider is above.
101
+ */
102
+ export function HostLayoutProvider({ layout, children }: HostLayoutProviderProps) {
103
+ return <HostLayoutContext.Provider value={layout}>{children}</HostLayoutContext.Provider>;
104
+ }
105
+
106
+ /** Reads the host layout descriptor. */
107
+ export function useHostLayout(): ResponsiveLayout {
108
+ return useContext(HostLayoutContext);
109
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Paseo Plugin Helper — Lifecycle (headless, client-safe, UI-free).
3
+ *
4
+ * Import from `paseo-plugin-helper/lifecycle` for the host-registration
5
+ * engines — composer pills, sidebar surfaces, workspace/agent panels — plus
6
+ * clipboard and haptics helpers. These are client-bundle safe (React Native,
7
+ * no `node:*`) but intentionally separate from `paseo-plugin-helper/core`,
8
+ * whose purity contract forbids `react-native`.
9
+ *
10
+ * The bespoke `client/` design system was removed (#938); this entry mounts
11
+ * the host theme itself and imports only the headless host seam from `core`.
12
+ */
13
+ export * from "./pill";
14
+ export * from "./surface";
15
+ export * from "./panel";
16
+ export * from "./clipboard";
17
+ export * from "./haptics";
18
+ export * from "./scroll-owner";
19
+ export * from "./host-theme";
20
+ export * from "./host-color";
21
+ export * from "./flair";
22
+ export * from "./providers";
23
+ export * from "./multi-host";
@@ -0,0 +1,66 @@
1
+ import * as pluginClient from "@getpaseo/plugin/client";
2
+ import type { PluginHostSummary } from "@getpaseo/plugin/client";
3
+
4
+ /**
5
+ * The desktop host bundle supplies `useHosts`/`getPaseoClient`; the mobile
6
+ * bundle omits them, so a direct named call throws `useHosts is not a
7
+ * function` and takes the whole surface with it (#1043, #1057-G). These seams
8
+ * reach the primitives through a feature-detected namespace so a surface
9
+ * degrades to "no hosts" instead of crashing the plugin.
10
+ */
11
+
12
+ /** The host summary shape `useHosts()` returns (the SDK's `PluginHostSummary`). */
13
+ export type OptionalHostSummary = PluginHostSummary;
14
+
15
+ /** The borrowed per-host client `getPaseoClient(serverId)` returns. */
16
+ export type OptionalPaseoClient = ReturnType<typeof pluginClient.getPaseoClient>;
17
+
18
+ interface MultiHostPrimitives {
19
+ useHosts?: () => readonly PluginHostSummary[];
20
+ getPaseoClient?: (serverId: string) => OptionalPaseoClient;
21
+ }
22
+
23
+ /**
24
+ * Read a function export without tripping a test runner's module-mock `get`
25
+ * trap. A partial mock throws when an unlisted export is read; `Object.keys`
26
+ * only asks for the keys the module has, so an absent primitive reads as
27
+ * `undefined` on mock and real host alike.
28
+ */
29
+ function readFunction<T>(source: unknown, key: string): T | undefined {
30
+ if (source === null || typeof source !== "object") return undefined;
31
+ if (!Object.keys(source).includes(key)) return undefined;
32
+ const value = (source as Record<string, unknown>)[key];
33
+ return typeof value === "function" ? (value as T) : undefined;
34
+ }
35
+
36
+ const hostPrimitives: MultiHostPrimitives = {
37
+ useHosts: readFunction(pluginClient, "useHosts"),
38
+ getPaseoClient: readFunction(pluginClient, "getPaseoClient"),
39
+ };
40
+ const EMPTY_HOSTS: readonly PluginHostSummary[] = [];
41
+ const noHosts = () => EMPTY_HOSTS;
42
+
43
+ // Hook identity is module-constant, so a given host calls the same function on
44
+ // every render and the hook order never changes.
45
+ const useHosts = hostPrimitives.useHosts ?? noHosts;
46
+
47
+ /** True when the running host supplies the multi-host primitives. */
48
+ export function isMultiHostSupported(): boolean {
49
+ return (
50
+ typeof hostPrimitives.useHosts === "function" &&
51
+ typeof hostPrimitives.getPaseoClient === "function"
52
+ );
53
+ }
54
+
55
+ /**
56
+ * `useHosts()` when the host supplies it, otherwise an empty host list, so a
57
+ * surface that enumerates hosts degrades instead of crashing the bundle.
58
+ */
59
+ export function useOptionalHosts(): readonly PluginHostSummary[] {
60
+ return useHosts();
61
+ }
62
+
63
+ /** Borrow an online host's API, or `undefined` when the host omits the seam. */
64
+ export function getOptionalPaseoClient(serverId: string): OptionalPaseoClient | undefined {
65
+ return hostPrimitives.getPaseoClient?.(serverId);
66
+ }
@@ -2,9 +2,9 @@ import React, { type ComponentType } from "react";
2
2
  import type {
3
3
  HostWorkspacePanelProps as PluginWorkspacePanelProps,
4
4
  HostAgentPanelProps as PluginAgentPanelProps,
5
- } from "./host";
6
- import { PluginThemeProvider } from "./theme/provider";
7
- import type { VisualFlair } from "./theme/flair";
5
+ } from "../core/host";
6
+ import type { VisualFlair } from "./flair";
7
+ import { RegistrarThemeScope } from "./providers";
8
8
 
9
9
  /**
10
10
  * Structural registrar interface satisfied by both Paseo v0.7 PluginContext
@@ -43,9 +43,9 @@ export function registerWorkspacePanel(
43
43
  const { id, title, icon, Component, flair, locations } = options;
44
44
 
45
45
  const WrappedComponent: ComponentType<PluginWorkspacePanelProps> = (props) => (
46
- <PluginThemeProvider theme={props.theme} layout={props.layout} flair={flair}>
46
+ <RegistrarThemeScope theme={props.theme} layout={props.layout} flair={flair}>
47
47
  <Component {...props} />
48
- </PluginThemeProvider>
48
+ </RegistrarThemeScope>
49
49
  );
50
50
 
51
51
  const registration = plugin.addWorkspacePanel({
@@ -74,9 +74,9 @@ export function registerAgentPanel(
74
74
  const { id, title, icon, Component, flair } = options;
75
75
 
76
76
  const WrappedComponent: ComponentType<PluginAgentPanelProps> = (props) => (
77
- <PluginThemeProvider theme={props.theme} layout={props.layout} flair={flair}>
77
+ <RegistrarThemeScope theme={props.theme} layout={props.layout} flair={flair}>
78
78
  <Component {...props} />
79
- </PluginThemeProvider>
79
+ </RegistrarThemeScope>
80
80
  );
81
81
 
82
82
  plugin.addWorkspacePanel({