@xpufx/paseo-top 0.4.4 → 0.4.7
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.
- package/OVERVIEW.md +18 -0
- package/client/host-ui.tsx +184 -0
- package/client/multi-host-view.tsx +57 -43
- package/client/multi-host.ts +36 -13
- package/client/pill-labels.ts +94 -1
- package/client/pill.tsx +692 -616
- package/client/resources-query.ts +1 -1
- package/client/settings-events.ts +1 -1
- package/client/settings-ui.tsx +86 -9
- package/client/surface.tsx +308 -276
- package/client/telemetry-copy.ts +8 -2
- package/client/telemetry.tsx +372 -321
- package/client/turn-panel.tsx +126 -104
- package/client/vendor/paseo-plugin-helper/{host.ts → core/host.ts} +9 -3
- package/client/vendor/paseo-plugin-helper/core/index.ts +76 -0
- package/client/vendor/paseo-plugin-helper/core/plugin-load-count.ts +89 -0
- package/client/vendor/paseo-plugin-helper/{query-refresh.ts → core/query-refresh.ts} +2 -2
- package/client/vendor/paseo-plugin-helper/{query.ts → core/query.ts} +2 -2
- package/client/vendor/paseo-plugin-helper/{settings.ts → core/settings.ts} +1 -1
- package/client/vendor/paseo-plugin-helper/{shared-settings.ts → core/shared-settings.ts} +2 -2
- package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/clipboard.ts +2 -2
- package/client/vendor/paseo-plugin-helper/lifecycle/flair.ts +26 -0
- package/client/vendor/paseo-plugin-helper/{theme/color-utils.ts → lifecycle/host-color.ts} +95 -38
- package/client/vendor/paseo-plugin-helper/lifecycle/host-theme.tsx +109 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/index.ts +23 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/multi-host.ts +66 -0
- package/client/vendor/paseo-plugin-helper/{panel.tsx → lifecycle/panel.tsx} +7 -7
- package/client/vendor/paseo-plugin-helper/{pill.tsx → lifecycle/pill.tsx} +51 -18
- package/client/vendor/paseo-plugin-helper/lifecycle/providers.tsx +46 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/scroll-owner.ts +26 -0
- package/client/vendor/paseo-plugin-helper/{surface.tsx → lifecycle/surface.tsx} +6 -6
- package/client/vendor/paseo-plugin-helper/ui/color.ts +1 -0
- package/client/vendor/paseo-plugin-helper/ui/content.tsx +1326 -0
- package/client/vendor/paseo-plugin-helper/ui/controls.tsx +1018 -0
- package/client/vendor/paseo-plugin-helper/{components/DataTable.tsx → ui/data.tsx} +50 -58
- package/client/vendor/paseo-plugin-helper/ui/index.ts +44 -0
- package/client/vendor/paseo-plugin-helper/ui/layout.tsx +263 -0
- package/client/vendor/paseo-plugin-helper/ui/modal.tsx +111 -0
- package/client/vendor/paseo-plugin-helper/ui/plugin-reload-banner.tsx +119 -0
- package/client/vendor/paseo-plugin-helper/{settings-screen.tsx → ui/settings-screen.tsx} +3 -3
- package/client/vendor/paseo-plugin-helper/ui/settings.ts +36 -0
- package/client/vendor/paseo-plugin-helper/ui/text.tsx +821 -0
- package/client/vendor/paseo-plugin-helper/ui/theme.tsx +1 -0
- package/client/vendor/permission-audit/client.tsx +706 -0
- package/client/vendor/permission-audit/index.tsx +1 -0
- package/index.client.tsx +58 -37
- package/index.server.ts +256 -234
- package/npm-shrinkwrap.json +44 -0
- package/package.json +8 -4
- package/paseo-plugin.json +2 -0
- package/screenshots/1-paseo-top-modal-system.jpg +0 -0
- package/screenshots/2-paseo-top-modal-workspace.jpg +0 -0
- package/screenshots/3-paseo-top-modal-settings.jpg +0 -0
- package/screenshots/4-paseo-top-modal-about.jpg +0 -0
- package/screenshots/paseo-top-dashboard.jpg +0 -0
- package/screenshots/paseo-top-general-view.jpg +0 -0
- package/server/resources.ts +98 -0
- package/server/vendor/paseo-plugin-helper/custom-pills.ts +70 -0
- package/server/vendor/paseo-plugin-helper/index.ts +2 -0
- package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
- package/server/vendor/paseo-plugin-helper/rpc-guard.ts +2 -2
- package/server/vendor/paseo-plugin-helper/storage-guard.ts +294 -0
- package/server/vendor/paseo-plugin-helper/storage.ts +36 -2
- package/server/vendor/paseo-plugin-helper/telemetry.ts +486 -0
- package/server/vendor/paseo-plugin-helper/tickets.ts +18 -67
- package/server/vendor/permission-audit/index.ts +1 -0
- package/server/vendor/permission-audit/server.ts +839 -0
- package/shared/helper-version.ts +1 -1
- package/shared/multi-host.ts +153 -159
- package/shared/resources.ts +145 -0
- package/shared/vendor/paseo-plugin-helper/async.ts +14 -7
- package/shared/vendor/paseo-plugin-helper/features.ts +244 -0
- package/shared/vendor/paseo-plugin-helper/forge.ts +27 -0
- package/shared/vendor/paseo-plugin-helper/index.ts +1 -0
- package/shared/vendor/paseo-plugin-helper/tickets.ts +122 -28
- package/shared/vendor/permission-audit/filter.ts +61 -0
- package/shared/vendor/permission-audit/index.ts +2 -0
- package/shared/vendor/permission-audit/shared.ts +119 -0
- package/shared/version.ts +1 -1
- package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +0 -493
- package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +0 -250
- package/client/vendor/paseo-plugin-helper/components/Badge.tsx +0 -168
- package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -204
- package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -257
- package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +0 -196
- package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +0 -277
- package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +0 -172
- package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +0 -180
- package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +0 -97
- package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +0 -70
- package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +0 -88
- package/client/vendor/paseo-plugin-helper/components/InteractiveRow.tsx +0 -142
- package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +0 -446
- package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +0 -247
- package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +0 -117
- package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +0 -53
- package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +0 -132
- package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -87
- package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -283
- package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +0 -80
- package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +0 -319
- package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +0 -150
- package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +0 -163
- package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +0 -157
- package/client/vendor/paseo-plugin-helper/components/index.ts +0 -26
- package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -225
- package/client/vendor/paseo-plugin-helper/forge-icon.tsx +0 -79
- package/client/vendor/paseo-plugin-helper/icon.tsx +0 -39
- package/client/vendor/paseo-plugin-helper/index.ts +0 -30
- package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +0 -49
- package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +0 -103
- package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +0 -65
- package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +0 -368
- package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +0 -51
- package/client/vendor/paseo-plugin-helper/layout/Row.tsx +0 -39
- package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +0 -39
- package/client/vendor/paseo-plugin-helper/layout/index.ts +0 -7
- package/client/vendor/paseo-plugin-helper/styles/index.ts +0 -1
- package/client/vendor/paseo-plugin-helper/styles/recipes.ts +0 -550
- package/client/vendor/paseo-plugin-helper/theme/flair.ts +0 -76
- package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +0 -121
- package/client/vendor/paseo-plugin-helper/theme/index.ts +0 -7
- package/client/vendor/paseo-plugin-helper/theme/provider.tsx +0 -178
- package/client/vendor/paseo-plugin-helper/theme/responsive.ts +0 -213
- package/client/vendor/paseo-plugin-helper/theme/tokens.ts +0 -200
- package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +0 -57
- package/client/vendor/paseo-plugin-helper/tickets.tsx +0 -766
- /package/client/vendor/paseo-plugin-helper/{command-center.ts → core/command-center.ts} +0 -0
- /package/client/vendor/paseo-plugin-helper/{snapshot.ts → core/snapshot.ts} +0 -0
- /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
|
-
*
|
|
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
|
|
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 "
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
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
|
-
<
|
|
46
|
+
<RegistrarThemeScope theme={props.theme} layout={props.layout} flair={flair}>
|
|
47
47
|
<Component {...props} />
|
|
48
|
-
</
|
|
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
|
-
<
|
|
77
|
+
<RegistrarThemeScope theme={props.theme} layout={props.layout} flair={flair}>
|
|
78
78
|
<Component {...props} />
|
|
79
|
-
</
|
|
79
|
+
</RegistrarThemeScope>
|
|
80
80
|
);
|
|
81
81
|
|
|
82
82
|
plugin.addWorkspacePanel({
|
|
@@ -14,16 +14,16 @@ import {
|
|
|
14
14
|
type HostPillProps,
|
|
15
15
|
type HostTheme,
|
|
16
16
|
type PluginCleanup,
|
|
17
|
-
} from "
|
|
17
|
+
} from "../core/host";
|
|
18
18
|
import type {
|
|
19
19
|
PluginButtonContentProps,
|
|
20
20
|
PluginButtonIconProps,
|
|
21
21
|
PluginButtonRegistration,
|
|
22
22
|
} from "@getpaseo/plugin/client";
|
|
23
|
-
import {
|
|
24
|
-
import
|
|
25
|
-
import {
|
|
26
|
-
import { reportSuppressed } from "
|
|
23
|
+
import type { VisualFlair } from "./flair";
|
|
24
|
+
import { ModalBodyScrollOwnerContext } from "./scroll-owner";
|
|
25
|
+
import { RegistrarThemeScope } from "./providers";
|
|
26
|
+
import { reportSuppressed } from "../../../../shared/vendor/paseo-plugin-helper/suppressed";
|
|
27
27
|
|
|
28
28
|
export interface RenderModalProps<TPayload = any> extends HostPillProps {
|
|
29
29
|
close: () => void;
|
|
@@ -423,9 +423,13 @@ export function registerComposerPill<TPayload = any>(
|
|
|
423
423
|
onStartShouldSetResponder={() => true}
|
|
424
424
|
onTouchEnd={stopBubbling}
|
|
425
425
|
{...(eventBoundaryProps as any)}
|
|
426
|
-
style={
|
|
426
|
+
style={
|
|
427
|
+
resolvePillModalScrollable(options.hostScroll)
|
|
428
|
+
? { width: "100%" }
|
|
429
|
+
: { flex: 1, minHeight: 0, width: "100%" }
|
|
430
|
+
}
|
|
427
431
|
>
|
|
428
|
-
<
|
|
432
|
+
<RegistrarThemeScope theme={theme} layout={layout} flair={options.flair}>
|
|
429
433
|
<ModalBodyScrollOwnerContext.Provider
|
|
430
434
|
value={resolvePillModalScrollable(options.hostScroll) ? "host" : "required"}
|
|
431
435
|
>
|
|
@@ -438,7 +442,7 @@ export function registerComposerPill<TPayload = any>(
|
|
|
438
442
|
close: () => setCenteredOpen(agentId, false),
|
|
439
443
|
})}
|
|
440
444
|
</ModalBodyScrollOwnerContext.Provider>
|
|
441
|
-
</
|
|
445
|
+
</RegistrarThemeScope>
|
|
442
446
|
</View>
|
|
443
447
|
) : null}
|
|
444
448
|
</Modal.Content>
|
|
@@ -470,11 +474,14 @@ export function registerComposerPill<TPayload = any>(
|
|
|
470
474
|
!props.layout.compact && options.popoverWidth
|
|
471
475
|
? Math.max(0, Math.min(options.popoverWidth, windowWidth - 24))
|
|
472
476
|
: undefined;
|
|
473
|
-
// The host remains the scroll owner, but
|
|
474
|
-
// establish the popover viewport.
|
|
475
|
-
//
|
|
476
|
-
//
|
|
477
|
-
const
|
|
477
|
+
// The host remains the scroll owner, but desktop needs a finite child extent to
|
|
478
|
+
// establish the popover viewport. Compact mobile presents a full-bleed sheet
|
|
479
|
+
// whose BottomSheetScrollView dynamically sizes and scrolls, so capping height
|
|
480
|
+
// here would clip long content instead of scrolling it.
|
|
481
|
+
const isMobile = props.layout.platform === "ios" || props.layout.platform === "android";
|
|
482
|
+
const frameMaxHeight = props.layout.compact && isMobile
|
|
483
|
+
? undefined
|
|
484
|
+
: Math.max(1, Math.min(560, windowHeight * 0.8));
|
|
478
485
|
// Layout effect, not effect: the flag must be set in the same commit the
|
|
479
486
|
// popover mounts, before any label timer can interleave. An effect runs
|
|
480
487
|
// after paint, leaving a window where one tick can still publish and
|
|
@@ -493,7 +500,7 @@ export function registerComposerPill<TPayload = any>(
|
|
|
493
500
|
host: props.host ?? { id: "", label: "" },
|
|
494
501
|
};
|
|
495
502
|
return (
|
|
496
|
-
<
|
|
503
|
+
<RegistrarThemeScope theme={props.theme} layout={props.layout} flair={options.flair}>
|
|
497
504
|
<View
|
|
498
505
|
style={{
|
|
499
506
|
...styles.popoverContainer,
|
|
@@ -505,7 +512,7 @@ export function registerComposerPill<TPayload = any>(
|
|
|
505
512
|
{options.renderModal?.({ ...pillProps, close: props.close })}
|
|
506
513
|
</ModalBodyScrollOwnerContext.Provider>
|
|
507
514
|
</View>
|
|
508
|
-
</
|
|
515
|
+
</RegistrarThemeScope>
|
|
509
516
|
);
|
|
510
517
|
}
|
|
511
518
|
|
|
@@ -632,6 +639,8 @@ export function registerComposerPill<TPayload = any>(
|
|
|
632
639
|
syncSharedLabelTimer();
|
|
633
640
|
}
|
|
634
641
|
|
|
642
|
+
const lifetime = new AbortController();
|
|
643
|
+
|
|
635
644
|
const unsubscribe = client.paseo.agents.subscribe((update) => {
|
|
636
645
|
if (disposed) return;
|
|
637
646
|
if ("agentId" in update && update.kind === "remove") {
|
|
@@ -645,14 +654,37 @@ export function registerComposerPill<TPayload = any>(
|
|
|
645
654
|
});
|
|
646
655
|
|
|
647
656
|
client.paseo.agents
|
|
648
|
-
.list()
|
|
657
|
+
.list({ subscribe: {}, signal: lifetime.signal })
|
|
649
658
|
.then((result) => {
|
|
650
659
|
if (disposed) return;
|
|
651
|
-
|
|
652
|
-
|
|
660
|
+
if (result.entries) {
|
|
661
|
+
for (const { agent } of result.entries) {
|
|
662
|
+
if (agent.workspaceId) addPill(agent.id, agent.workspaceId);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
if (result.subscription) {
|
|
666
|
+
result.subscription.subscribe({
|
|
667
|
+
snapshot: ({ entries }: { entries: Array<{ agent: { id: string; workspaceId?: string | null } }> }) => {
|
|
668
|
+
if (disposed) return;
|
|
669
|
+
for (const { agent } of entries) {
|
|
670
|
+
if (agent.workspaceId) addPill(agent.id, agent.workspaceId);
|
|
671
|
+
}
|
|
672
|
+
},
|
|
673
|
+
update: (message: any) => {
|
|
674
|
+
if (disposed) return;
|
|
675
|
+
if (message?.type !== "agent_update") return;
|
|
676
|
+
const update = message.payload;
|
|
677
|
+
if ("agentId" in update && update.kind === "remove") {
|
|
678
|
+
removePill(update.agentId);
|
|
679
|
+
} else if ("agent" in update && update.agent?.workspaceId) {
|
|
680
|
+
addPill(update.agent.id, update.agent.workspaceId);
|
|
681
|
+
}
|
|
682
|
+
},
|
|
683
|
+
});
|
|
653
684
|
}
|
|
654
685
|
})
|
|
655
686
|
.catch((error) => {
|
|
687
|
+
if (lifetime.signal.aborted || disposed) return;
|
|
656
688
|
// Silently dropping this leaves a plugin with zero pills and no clue why.
|
|
657
689
|
reportSuppressed(console, "registerComposerPill agents.list() failed", error, "warn");
|
|
658
690
|
});
|
|
@@ -660,6 +692,7 @@ export function registerComposerPill<TPayload = any>(
|
|
|
660
692
|
return () => {
|
|
661
693
|
if (disposed) return;
|
|
662
694
|
disposed = true;
|
|
695
|
+
lifetime.abort();
|
|
663
696
|
unsubscribe();
|
|
664
697
|
if (sharedLabelTimer) {
|
|
665
698
|
clearInterval(sharedLabelTimer);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React, { type ComponentType, type ReactNode } from "react";
|
|
2
|
+
import type { PluginTheme } from "../../../../shared/vendor/paseo-plugin-helper/types";
|
|
3
|
+
import type { HostLayout } from "../core/host";
|
|
4
|
+
import type { VisualFlair } from "./flair";
|
|
5
|
+
import { HostThemeProvider } from "./host-theme";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Presentational seam for the lifecycle registration engines.
|
|
9
|
+
*
|
|
10
|
+
* The bespoke `client/` design system was removed (#938), so no installer
|
|
11
|
+
* remains: `RegistrarThemeScope` mounts only `HostThemeProvider` (the host
|
|
12
|
+
* `theme` prop drives every ui/ adapter). The legacy-provider hook stays for
|
|
13
|
+
* callers that mounted their own theme provider before the removal.
|
|
14
|
+
*/
|
|
15
|
+
export interface RegistrarThemeProps {
|
|
16
|
+
theme: PluginTheme;
|
|
17
|
+
layout?: HostLayout;
|
|
18
|
+
flair?: Partial<VisualFlair>;
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type RegistrarLegacyThemeProvider = ComponentType<RegistrarThemeProps>;
|
|
23
|
+
|
|
24
|
+
let legacyThemeProvider: RegistrarLegacyThemeProvider | undefined;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Installed once by a legacy theme shim. Keeping it out of the `lifecycle/`
|
|
28
|
+
* entry's runtime imports is what lets that entry stay free of any UI kit.
|
|
29
|
+
*/
|
|
30
|
+
export function setRegistrarLegacyThemeProvider(
|
|
31
|
+
provider: RegistrarLegacyThemeProvider | undefined,
|
|
32
|
+
): void {
|
|
33
|
+
legacyThemeProvider = provider;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Mounts the host theme (always) and the legacy provider (when installed). */
|
|
37
|
+
export function RegistrarThemeScope({ theme, layout, flair, children }: RegistrarThemeProps) {
|
|
38
|
+
const hostTheme = <HostThemeProvider theme={theme}>{children}</HostThemeProvider>;
|
|
39
|
+
const Legacy = legacyThemeProvider;
|
|
40
|
+
if (!Legacy) return hostTheme;
|
|
41
|
+
return (
|
|
42
|
+
<Legacy theme={theme} layout={layout} flair={flair}>
|
|
43
|
+
{hostTheme}
|
|
44
|
+
</Legacy>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Scroll-ownership signal emitted by the lifecycle registration engines.
|
|
5
|
+
*
|
|
6
|
+
* - `"helper"` — the default; the content owns its own scrolling.
|
|
7
|
+
* - `"host"` — an ancestor host view already provides the one scroller
|
|
8
|
+
* (0.8 composer popovers), so the content renders plain.
|
|
9
|
+
* - `"required"` — the ancestor has NO host scroller and the content is
|
|
10
|
+
* host-sized, so it MUST own the scroll on every surface. Set by
|
|
11
|
+
* `registerSidebarSurface` (a plugin surface is a full host page whose body
|
|
12
|
+
* is not wrapped in a host scroller).
|
|
13
|
+
* - `"popover"` — the host anchored-popover already supplies the sole scroller.
|
|
14
|
+
*
|
|
15
|
+
* The context lives in `lifecycle/` so the registration engines can mark the
|
|
16
|
+
* subtrees they render without importing any UI kit. It survived the removal of
|
|
17
|
+
* the bespoke `ModalBody` reader (#938) as a signal plugins and the host can
|
|
18
|
+
* still consume.
|
|
19
|
+
*/
|
|
20
|
+
export type ModalBodyScrollOwner = "helper" | "host" | "required" | "popover";
|
|
21
|
+
|
|
22
|
+
const SCROLL_OWNER_CONTEXT_KEY = Symbol.for("paseo.modalBodyScrollOwnerContext");
|
|
23
|
+
|
|
24
|
+
export const ModalBodyScrollOwnerContext: React.Context<ModalBodyScrollOwner> =
|
|
25
|
+
((globalThis as any)[SCROLL_OWNER_CONTEXT_KEY] ??= createContext<ModalBodyScrollOwner>("helper"));
|
|
26
|
+
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React, { type ComponentType } from "react";
|
|
2
2
|
import { View } from "react-native";
|
|
3
|
-
import type { HostSurfaceProps as PluginSurfaceProps } from "
|
|
4
|
-
import {
|
|
5
|
-
import { ModalBodyScrollOwnerContext } from "./
|
|
6
|
-
import
|
|
3
|
+
import type { HostSurfaceProps as PluginSurfaceProps } from "../core/host";
|
|
4
|
+
import type { VisualFlair } from "./flair";
|
|
5
|
+
import { ModalBodyScrollOwnerContext } from "./scroll-owner";
|
|
6
|
+
import { RegistrarThemeScope } from "./providers";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Structural registrar interface satisfied by both Paseo v0.7 PluginContext
|
|
@@ -101,7 +101,7 @@ export function registerSidebarSurface(
|
|
|
101
101
|
} = options;
|
|
102
102
|
|
|
103
103
|
const WrappedComponent: ComponentType<PluginSurfaceProps> = (props) => (
|
|
104
|
-
<
|
|
104
|
+
<RegistrarThemeScope theme={props.theme} layout={props.layout} flair={flair}>
|
|
105
105
|
<ModalBodyScrollOwnerContext.Provider value="required">
|
|
106
106
|
{maxContentWidth === false ? (
|
|
107
107
|
<Component {...props} />
|
|
@@ -111,7 +111,7 @@ export function registerSidebarSurface(
|
|
|
111
111
|
</SurfaceContentColumn>
|
|
112
112
|
)}
|
|
113
113
|
</ModalBodyScrollOwnerContext.Provider>
|
|
114
|
-
</
|
|
114
|
+
</RegistrarThemeScope>
|
|
115
115
|
);
|
|
116
116
|
|
|
117
117
|
const surfaceRegistration = plugin.addSurface(id, WrappedComponent);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../lifecycle/host-color";
|