@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.
- package/OVERVIEW.md +17 -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 +124 -7
- package/client/pill.tsx +724 -669
- package/client/resources-query.ts +1 -1
- package/client/settings-events.ts +33 -0
- package/client/settings-ui.tsx +86 -9
- package/client/surface.tsx +310 -274
- 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} +21 -61
- 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} +118 -338
- 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/lifecycle/surface.tsx +140 -0
- 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} +55 -47
- 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 -227
- package/package.json +14 -2
- 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 +4 -0
- package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
- package/server/vendor/paseo-plugin-helper/mcp-injection.ts +20 -2
- package/server/vendor/paseo-plugin-helper/plugins.ts +184 -19
- 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 +1232 -0
- package/server/vendor/permission-audit/index.ts +1 -0
- package/server/vendor/permission-audit/server.ts +839 -0
- package/shared/helper-version.ts +20 -0
- package/shared/multi-host.ts +156 -155
- package/shared/resources.ts +146 -1
- 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 +2 -0
- package/shared/vendor/paseo-plugin-helper/tickets.ts +1104 -0
- 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 -156
- package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -184
- package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -225
- 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 -75
- 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 -118
- package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -80
- package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -215
- 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 -25
- package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -224
- 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 -29
- 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 -378
- 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/surface.tsx +0 -80
- 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/{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
|
@@ -2,34 +2,28 @@ import React, {
|
|
|
2
2
|
useEffect,
|
|
3
3
|
useLayoutEffect,
|
|
4
4
|
useMemo,
|
|
5
|
-
useState,
|
|
6
5
|
useSyncExternalStore,
|
|
7
6
|
type ComponentType,
|
|
8
7
|
type ReactNode,
|
|
9
8
|
} from "react";
|
|
10
|
-
import { StyleSheet,
|
|
9
|
+
import { StyleSheet, useWindowDimensions, View } from "react-native";
|
|
11
10
|
import {
|
|
12
11
|
getClientHost,
|
|
13
12
|
type ComposerPillRegistrar,
|
|
14
|
-
type ComposerPillRegistration,
|
|
15
|
-
type ComposerPillRegistrationHandle,
|
|
16
13
|
type HostLayout,
|
|
17
14
|
type HostPillProps,
|
|
18
15
|
type HostTheme,
|
|
19
16
|
type PluginCleanup,
|
|
20
|
-
} from "
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
close: () => void;
|
|
31
|
-
toggle: (payload?: TPayload) => void;
|
|
32
|
-
}
|
|
17
|
+
} from "../core/host";
|
|
18
|
+
import type {
|
|
19
|
+
PluginButtonContentProps,
|
|
20
|
+
PluginButtonIconProps,
|
|
21
|
+
PluginButtonRegistration,
|
|
22
|
+
} from "@getpaseo/plugin/client";
|
|
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";
|
|
33
27
|
|
|
34
28
|
export interface RenderModalProps<TPayload = any> extends HostPillProps {
|
|
35
29
|
close: () => void;
|
|
@@ -69,8 +63,6 @@ export function resolvePillModalScrollable(hostScroll?: boolean): boolean {
|
|
|
69
63
|
return hostScroll === true;
|
|
70
64
|
}
|
|
71
65
|
|
|
72
|
-
let probeSequence = 0;
|
|
73
|
-
|
|
74
66
|
const NOOP_SUBSCRIBE = (): (() => void) => () => {};
|
|
75
67
|
|
|
76
68
|
export interface RegisterComposerPillOptions<TPayload = any> {
|
|
@@ -118,48 +110,25 @@ export interface RegisterComposerPillOptions<TPayload = any> {
|
|
|
118
110
|
flair?: Partial<VisualFlair>;
|
|
119
111
|
|
|
120
112
|
/**
|
|
121
|
-
*
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
* Optional compact badge text shown inside the default pill in compact mode. Defaults to `badgeText`.
|
|
127
|
-
*/
|
|
128
|
-
compactBadgeText?: string;
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Optional callback to resolve default payload when the outer host pill is clicked.
|
|
132
|
-
* Receives agentId and workspaceId.
|
|
133
|
-
*/
|
|
134
|
-
resolveDefaultPayload?: (context: { agentId: string; workspaceId: string }) => TPayload | undefined;
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Custom pill body renderer if you want to replace the default pill layout.
|
|
138
|
-
* Receives `isOpen`, `open`, `close`, and `toggle` along with standard pill props.\
|
|
139
|
-
*/
|
|
140
|
-
renderPill?: (props: RenderPillProps<TPayload>) => ReactNode;
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Resolves the live pill label (and optionally icon) on button-shaped hosts (Paseo 0.8+), where the
|
|
144
|
-
* pill body is host-rendered from a static `label` and `icon` string and `renderPill`
|
|
145
|
-
* never mounts. Called once at registration and then every
|
|
146
|
-
* `refreshIntervalMs`. Keep it cheap and synchronous when possible; async
|
|
147
|
-
* resolvers are awaited. Returning `undefined` leaves the current label/icon.
|
|
113
|
+
* Resolves the live pill label (and optionally icon). The host renders the
|
|
114
|
+
* pill body itself, so this is the only channel for live pill text. Called
|
|
115
|
+
* once at registration and then every `refreshIntervalMs`. Keep it cheap and
|
|
116
|
+
* synchronous when possible; async resolvers are awaited. Returning
|
|
117
|
+
* `undefined` leaves the current label/icon.
|
|
148
118
|
* Can return a plain string (label) or an object `{ label?: string; icon?: string }`.
|
|
149
119
|
* Cycle modes can advance rotation state on each call.
|
|
150
120
|
*/
|
|
151
121
|
resolveLabel?: PillLabelResolver;
|
|
152
122
|
|
|
153
123
|
/**
|
|
154
|
-
* Optional standalone resolver for the
|
|
155
|
-
*
|
|
124
|
+
* Optional standalone resolver for the pill icon. Evaluated alongside
|
|
125
|
+
* `resolveLabel` on each tick.
|
|
156
126
|
*/
|
|
157
127
|
resolveIcon?: PillIconResolver;
|
|
158
128
|
|
|
159
129
|
/**
|
|
160
|
-
* Poll interval for `resolveLabel
|
|
130
|
+
* Poll interval for `resolveLabel`. Defaults to 5000ms
|
|
161
131
|
* when `resolveLabel` or `resolveIcon` is set. Set to 0 to resolve once at registration.
|
|
162
|
-
* Ignored on legacy hosts (their `renderPill` re-renders via React state).
|
|
163
132
|
*/
|
|
164
133
|
refreshIntervalMs?: number;
|
|
165
134
|
|
|
@@ -178,31 +147,28 @@ export interface RegisterComposerPillOptions<TPayload = any> {
|
|
|
178
147
|
popoverWidth?: number;
|
|
179
148
|
|
|
180
149
|
/**
|
|
181
|
-
* Renders the content inside the
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
* popovers, so live pill text comes from `resolveLabel` instead.
|
|
150
|
+
* Renders the content inside the pill's surface.
|
|
151
|
+
* The host owns the surface: with the default `"popover"` presentation it
|
|
152
|
+
* anchors this output to the pill at the host surface width (expect a
|
|
153
|
+
* narrow column, not a wide modal; keep content vertically stacked and
|
|
154
|
+
* reflowing), and with `"centered"` it renders it from the pill's icon.
|
|
155
|
+
* Live pill text therefore comes from `resolveLabel`, not from this renderer.
|
|
188
156
|
*
|
|
189
|
-
*
|
|
190
|
-
* on the modal paths (legacy + centered). Never render another
|
|
191
|
-
* `<Modal.Content>` here — use `HostModalSection` from
|
|
157
|
+
* Never render a host `<Modal.Content>` here — use `HostModalSection` from
|
|
192
158
|
* `paseo-plugin-helper/ui` for fluid content. (`HostModalContent` is only
|
|
193
159
|
* for plugins that open their OWN host `<Modal>`.)
|
|
194
160
|
*/
|
|
195
161
|
renderModal?: (props: RenderModalProps<TPayload>) => ReactNode;
|
|
196
162
|
|
|
197
163
|
/**
|
|
198
|
-
* Host-owned scroll for the
|
|
199
|
-
* - `false` (default
|
|
164
|
+
* Host-owned scroll for the centered modal path (#219).
|
|
165
|
+
* - `false` (default): the wrapper renders
|
|
200
166
|
* `<Modal.Content scrollable={false}>` (bounded dialog) and
|
|
201
167
|
* `ModalBody`-based content owns the one scroller.
|
|
202
168
|
* - `true`: the wrapper renders `<Modal.Content scrollable={true}>` so the
|
|
203
169
|
* host scrolls, and `renderModal` must provide fluid content with NO
|
|
204
170
|
* nested `<Modal.Content>` or scroller (`HostModalSection`).
|
|
205
|
-
* Exactly one `<Modal.Content>` is rendered in both modes. The
|
|
171
|
+
* Exactly one `<Modal.Content>` is rendered in both modes. The popover
|
|
206
172
|
* path is unaffected (plain host-owned container either way).
|
|
207
173
|
*/
|
|
208
174
|
hostScroll?: boolean;
|
|
@@ -228,9 +194,9 @@ export interface RegisterComposerPillOptions<TPayload = any> {
|
|
|
228
194
|
presentation?: "popover" | "centered";
|
|
229
195
|
|
|
230
196
|
/**
|
|
231
|
-
* Called when a pill cannot be registered on the current host
|
|
232
|
-
*
|
|
233
|
-
*
|
|
197
|
+
* Called when a pill cannot be registered on the current host. Reporting
|
|
198
|
+
* instead of throwing keeps the rest of the plugin client alive; render the
|
|
199
|
+
* message in your own panel to make it visible.
|
|
234
200
|
*/
|
|
235
201
|
onError?: (info: { agentId: string; workspaceId: string; error: Error }) => void;
|
|
236
202
|
}
|
|
@@ -238,32 +204,27 @@ export interface RegisterComposerPillOptions<TPayload = any> {
|
|
|
238
204
|
/**
|
|
239
205
|
* Registers an agent-scoped composer pill and modal lifecycle.
|
|
240
206
|
* Manages agent subscription events, unmount cleanup, and pill-to-modal activation.
|
|
241
|
-
*
|
|
242
|
-
* Works against both host generations: legacy `{Component, onPress}` pills
|
|
243
|
-
* (Paseo 0.7 and beta apps) and `button`-descriptor pills (Paseo 0.8+), detected
|
|
244
|
-
* once per call with a throwaway probe registration that is removed immediately.
|
|
245
207
|
*/
|
|
246
208
|
export function registerComposerPill<TPayload = any>(
|
|
247
209
|
client: ComposerPillRegistrar,
|
|
248
210
|
options: RegisterComposerPillOptions<TPayload>,
|
|
249
211
|
): PluginCleanup {
|
|
250
212
|
const { Icon, Modal } = getClientHost();
|
|
251
|
-
const openers = new Map<string, (payload?: TPayload) => void>();
|
|
252
213
|
const pills = new Map<
|
|
253
214
|
string,
|
|
254
215
|
{
|
|
255
216
|
dispose: () => void;
|
|
256
217
|
workspaceId: string;
|
|
257
|
-
registration?:
|
|
218
|
+
registration?: PluginButtonRegistration;
|
|
258
219
|
}
|
|
259
220
|
>();
|
|
260
221
|
const pushedDescriptors = new WeakMap<object, { label?: string; icon?: string }>();
|
|
261
|
-
// A
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
222
|
+
// A pill can only be resolved while the host actually renders it. The host
|
|
223
|
+
// mounts the pill's custom icon component for every visible composer pill, and
|
|
224
|
+
// that mount is the plugin's only visibility signal: its mount/unmount
|
|
225
|
+
// brackets the pill's on-screen lifetime. Polling every registered agent
|
|
226
|
+
// regardless of that signal ran one ticker per agent in the install, which
|
|
227
|
+
// stampedes the daemon on a host with many agents.
|
|
267
228
|
const visiblePillMounts = new Map<string, number>();
|
|
268
229
|
// Agent ids whose anchored popover is currently mounted. While a popover is
|
|
269
230
|
// open the host re-renders the whole composer on every agent-stream message;
|
|
@@ -280,7 +241,6 @@ export function registerComposerPill<TPayload = any>(
|
|
|
280
241
|
const centeredOpenAgents = new Set<string>();
|
|
281
242
|
const centeredListenersByAgent = new Map<string, Set<() => void>>();
|
|
282
243
|
let sharedLabelTimer: ReturnType<typeof setInterval> | null = null;
|
|
283
|
-
let detectedShape: "button" | "legacy" | null = null;
|
|
284
244
|
let disposed = false;
|
|
285
245
|
|
|
286
246
|
function defaultPillIcon(): string {
|
|
@@ -385,22 +345,8 @@ export function registerComposerPill<TPayload = any>(
|
|
|
385
345
|
* `registration.update({ icon })`, so the probe component never gets
|
|
386
346
|
* replaced by a plain string and the mount signal survives every icon swap.
|
|
387
347
|
*/
|
|
388
|
-
function makePillIcon(agentId: string): ComponentType<{
|
|
389
|
-
|
|
390
|
-
color?: string;
|
|
391
|
-
theme?: HostTheme;
|
|
392
|
-
layout?: HostLayout;
|
|
393
|
-
host?: { id: string; label: string };
|
|
394
|
-
workspaceId?: string;
|
|
395
|
-
}> {
|
|
396
|
-
return function PillVisibilityIcon(props: {
|
|
397
|
-
size?: number;
|
|
398
|
-
color?: string;
|
|
399
|
-
theme?: HostTheme;
|
|
400
|
-
layout?: HostLayout;
|
|
401
|
-
host?: { id: string; label: string };
|
|
402
|
-
workspaceId?: string;
|
|
403
|
-
}) {
|
|
348
|
+
function makePillIcon(agentId: string): ComponentType<PluginButtonIconProps> {
|
|
349
|
+
return function PillVisibilityIcon(props: PluginButtonIconProps) {
|
|
404
350
|
const subscribe = useMemo(() => subscribePillIcon(agentId), [agentId]);
|
|
405
351
|
const name = useSyncExternalStore(
|
|
406
352
|
subscribe,
|
|
@@ -426,7 +372,7 @@ export function registerComposerPill<TPayload = any>(
|
|
|
426
372
|
|
|
427
373
|
const icon = <Icon name={name} size={props.size ?? 14} color={props.color ?? ""} />;
|
|
428
374
|
const { theme, layout } = props;
|
|
429
|
-
if (!centered || !options.renderModal
|
|
375
|
+
if (!centered || !options.renderModal) return icon;
|
|
430
376
|
|
|
431
377
|
const modalIconElement = React.isValidElement(options.modalIcon) ? (
|
|
432
378
|
options.modalIcon
|
|
@@ -477,21 +423,26 @@ export function registerComposerPill<TPayload = any>(
|
|
|
477
423
|
onStartShouldSetResponder={() => true}
|
|
478
424
|
onTouchEnd={stopBubbling}
|
|
479
425
|
{...(eventBoundaryProps as any)}
|
|
426
|
+
style={
|
|
427
|
+
resolvePillModalScrollable(options.hostScroll)
|
|
428
|
+
? { width: "100%" }
|
|
429
|
+
: { flex: 1, minHeight: 0, width: "100%" }
|
|
430
|
+
}
|
|
480
431
|
>
|
|
481
|
-
<
|
|
432
|
+
<RegistrarThemeScope theme={theme} layout={layout} flair={options.flair}>
|
|
482
433
|
<ModalBodyScrollOwnerContext.Provider
|
|
483
434
|
value={resolvePillModalScrollable(options.hostScroll) ? "host" : "required"}
|
|
484
435
|
>
|
|
485
436
|
{options.renderModal({
|
|
486
437
|
agentId,
|
|
487
|
-
workspaceId: props.workspaceId
|
|
438
|
+
workspaceId: props.workspaceId,
|
|
488
439
|
theme,
|
|
489
440
|
layout,
|
|
490
441
|
host: props.host ?? { id: "", label: "" },
|
|
491
442
|
close: () => setCenteredOpen(agentId, false),
|
|
492
443
|
})}
|
|
493
444
|
</ModalBodyScrollOwnerContext.Provider>
|
|
494
|
-
</
|
|
445
|
+
</RegistrarThemeScope>
|
|
495
446
|
</View>
|
|
496
447
|
) : null}
|
|
497
448
|
</Modal.Content>
|
|
@@ -502,7 +453,7 @@ export function registerComposerPill<TPayload = any>(
|
|
|
502
453
|
};
|
|
503
454
|
}
|
|
504
455
|
|
|
505
|
-
//
|
|
456
|
+
// Popover scroll ownership: Paseo's MenuSurface/FloatingScrollView or
|
|
506
457
|
// BottomSheetScrollView already owns the viewport. Keep this wrapper plain,
|
|
507
458
|
// unconstrained, and mark the subtree so ModalBody does not add a second
|
|
508
459
|
// ScrollView. Fixed heights or overflow clipping here cut off mobile content.
|
|
@@ -523,11 +474,14 @@ export function registerComposerPill<TPayload = any>(
|
|
|
523
474
|
!props.layout.compact && options.popoverWidth
|
|
524
475
|
? Math.max(0, Math.min(options.popoverWidth, windowWidth - 24))
|
|
525
476
|
: undefined;
|
|
526
|
-
// The host remains the scroll owner, but
|
|
527
|
-
// establish the popover viewport.
|
|
528
|
-
//
|
|
529
|
-
//
|
|
530
|
-
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));
|
|
531
485
|
// Layout effect, not effect: the flag must be set in the same commit the
|
|
532
486
|
// popover mounts, before any label timer can interleave. An effect runs
|
|
533
487
|
// after paint, leaving a window where one tick can still publish and
|
|
@@ -546,7 +500,7 @@ export function registerComposerPill<TPayload = any>(
|
|
|
546
500
|
host: props.host ?? { id: "", label: "" },
|
|
547
501
|
};
|
|
548
502
|
return (
|
|
549
|
-
<
|
|
503
|
+
<RegistrarThemeScope theme={props.theme} layout={props.layout} flair={options.flair}>
|
|
550
504
|
<View
|
|
551
505
|
style={{
|
|
552
506
|
...styles.popoverContainer,
|
|
@@ -558,102 +512,11 @@ export function registerComposerPill<TPayload = any>(
|
|
|
558
512
|
{options.renderModal?.({ ...pillProps, close: props.close })}
|
|
559
513
|
</ModalBodyScrollOwnerContext.Provider>
|
|
560
514
|
</View>
|
|
561
|
-
</
|
|
515
|
+
</RegistrarThemeScope>
|
|
562
516
|
);
|
|
563
517
|
}
|
|
564
518
|
|
|
565
|
-
function
|
|
566
|
-
const [open, setOpen] = useState(false);
|
|
567
|
-
const [payload, setPayload] = useState<TPayload | undefined>(undefined);
|
|
568
|
-
|
|
569
|
-
useEffect(() => {
|
|
570
|
-
openers.set(props.agentId, (incomingPayload?: TPayload) => {
|
|
571
|
-
setPayload(incomingPayload);
|
|
572
|
-
setOpen(true);
|
|
573
|
-
});
|
|
574
|
-
return () => {
|
|
575
|
-
openers.delete(props.agentId);
|
|
576
|
-
};
|
|
577
|
-
}, [props.agentId]);
|
|
578
|
-
|
|
579
|
-
const effectiveModalTitle = options.modalTitle ?? options.title;
|
|
580
|
-
|
|
581
|
-
const modalIconElement = useMemo(() => {
|
|
582
|
-
if (React.isValidElement(options.modalIcon)) {
|
|
583
|
-
return options.modalIcon;
|
|
584
|
-
}
|
|
585
|
-
const iconName =
|
|
586
|
-
typeof options.modalIcon === "string" ? options.modalIcon : options.icon;
|
|
587
|
-
if (iconName) {
|
|
588
|
-
return <Icon name={iconName} size={16} color={props.theme.colors.foreground} />;
|
|
589
|
-
}
|
|
590
|
-
return undefined;
|
|
591
|
-
}, [options.modalIcon, options.icon, props.theme.colors.foreground]);
|
|
592
|
-
|
|
593
|
-
const renderPillProps: RenderPillProps<TPayload> = {
|
|
594
|
-
...props,
|
|
595
|
-
isOpen: open,
|
|
596
|
-
open: (customPayload?: TPayload) => {
|
|
597
|
-
setPayload(customPayload);
|
|
598
|
-
setOpen(true);
|
|
599
|
-
},
|
|
600
|
-
close: () => setOpen(false),
|
|
601
|
-
toggle: (customPayload?: TPayload) => {
|
|
602
|
-
setPayload(customPayload);
|
|
603
|
-
setOpen((prev) => !prev);
|
|
604
|
-
},
|
|
605
|
-
};
|
|
606
|
-
|
|
607
|
-
return (
|
|
608
|
-
<PluginThemeProvider theme={props.theme} layout={props.layout} flair={options.flair}>
|
|
609
|
-
{options.renderPill ? (
|
|
610
|
-
options.renderPill(renderPillProps)
|
|
611
|
-
) : (
|
|
612
|
-
<DefaultPillBody
|
|
613
|
-
title={options.title}
|
|
614
|
-
compactTitle={options.compactTitle}
|
|
615
|
-
icon={options.icon}
|
|
616
|
-
compactIcon={options.compactIcon}
|
|
617
|
-
badgeText={options.badgeText}
|
|
618
|
-
compactBadgeText={options.compactBadgeText}
|
|
619
|
-
theme={props.theme}
|
|
620
|
-
/>
|
|
621
|
-
)}
|
|
622
|
-
|
|
623
|
-
<Modal
|
|
624
|
-
title={effectiveModalTitle}
|
|
625
|
-
icon={modalIconElement}
|
|
626
|
-
open={open}
|
|
627
|
-
onOpenChange={(nextOpen) => {
|
|
628
|
-
setOpen(nextOpen);
|
|
629
|
-
if (!nextOpen) {
|
|
630
|
-
setPayload(undefined);
|
|
631
|
-
}
|
|
632
|
-
}}
|
|
633
|
-
>
|
|
634
|
-
<Modal.Content scrollable={resolvePillModalScrollable(options.hostScroll)}>
|
|
635
|
-
{open ? (
|
|
636
|
-
<PluginThemeProvider theme={props.theme} layout={props.layout} flair={options.flair}>
|
|
637
|
-
<ModalBodyScrollOwnerContext.Provider
|
|
638
|
-
value={resolvePillModalScrollable(options.hostScroll) ? "host" : "required"}
|
|
639
|
-
>
|
|
640
|
-
{options.renderModal?.({
|
|
641
|
-
...props,
|
|
642
|
-
close: () => setOpen(false),
|
|
643
|
-
payload,
|
|
644
|
-
})}
|
|
645
|
-
</ModalBodyScrollOwnerContext.Provider>
|
|
646
|
-
</PluginThemeProvider>
|
|
647
|
-
) : null}
|
|
648
|
-
</Modal.Content>
|
|
649
|
-
|
|
650
|
-
</Modal>
|
|
651
|
-
</PluginThemeProvider>
|
|
652
|
-
);
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
function toCleanup(registration: ComposerPillRegistration): PluginCleanup {
|
|
656
|
-
if (typeof registration === "function") return registration;
|
|
519
|
+
function toCleanup(registration: PluginButtonRegistration): PluginCleanup {
|
|
657
520
|
return () => registration.remove();
|
|
658
521
|
}
|
|
659
522
|
|
|
@@ -675,9 +538,8 @@ export function registerComposerPill<TPayload = any>(
|
|
|
675
538
|
function resolveAndPushLabel(
|
|
676
539
|
agentId: string,
|
|
677
540
|
workspaceId: string,
|
|
678
|
-
registration:
|
|
541
|
+
registration: PluginButtonRegistration,
|
|
679
542
|
): void {
|
|
680
|
-
if (typeof registration === "function") return;
|
|
681
543
|
if (!options.resolveLabel && !options.resolveIcon) return;
|
|
682
544
|
const ctx = { agentId, workspaceId };
|
|
683
545
|
Promise.all([
|
|
@@ -727,83 +589,41 @@ export function registerComposerPill<TPayload = any>(
|
|
|
727
589
|
});
|
|
728
590
|
}
|
|
729
591
|
|
|
730
|
-
function
|
|
731
|
-
|
|
732
|
-
const probeId = `php-probe-${probeSequence}`;
|
|
592
|
+
function addPill(agentId: string, workspaceId: string) {
|
|
593
|
+
if (disposed || pills.has(agentId)) return;
|
|
733
594
|
try {
|
|
734
595
|
const registration = client.addComposerPill({
|
|
735
|
-
id:
|
|
596
|
+
id: options.id,
|
|
736
597
|
workspaceId,
|
|
737
598
|
agentId,
|
|
738
599
|
button: {
|
|
739
|
-
title:
|
|
740
|
-
icon
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
600
|
+
title: options.title,
|
|
601
|
+
// The icon is both the visibility signal and the live-icon renderer.
|
|
602
|
+
icon: makePillIcon(agentId),
|
|
603
|
+
label: options.title,
|
|
604
|
+
behavior: options.onPress
|
|
605
|
+
? { kind: "action", onPress: () => void options.onPress?.() }
|
|
606
|
+
: options.presentation === "centered"
|
|
607
|
+
? {
|
|
608
|
+
kind: "action",
|
|
609
|
+
onPress: () => setCenteredOpen(agentId, !centeredOpenAgents.has(agentId)),
|
|
610
|
+
}
|
|
611
|
+
: {
|
|
612
|
+
kind: "popover",
|
|
613
|
+
Content: PillPopoverContent as ComponentType<PluginButtonContentProps>,
|
|
614
|
+
},
|
|
745
615
|
},
|
|
746
616
|
});
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
} catch {
|
|
750
|
-
return "legacy";
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
function addPill(agentId: string, workspaceId: string) {
|
|
755
|
-
if (disposed || pills.has(agentId)) return;
|
|
756
|
-
try {
|
|
757
|
-
if (!detectedShape) {
|
|
758
|
-
detectedShape = detectShape(agentId, workspaceId);
|
|
759
|
-
}
|
|
760
|
-
if (detectedShape === "button") {
|
|
761
|
-
const registration = client.addComposerPill({
|
|
762
|
-
id: options.id,
|
|
763
|
-
workspaceId,
|
|
764
|
-
agentId,
|
|
765
|
-
button: {
|
|
766
|
-
title: options.title,
|
|
767
|
-
// The probe is the visibility signal and the live-icon renderer.
|
|
768
|
-
icon: makePillIcon(agentId),
|
|
769
|
-
label: options.title,
|
|
770
|
-
behavior: options.onPress
|
|
771
|
-
? { kind: "action", onPress: () => void options.onPress?.() }
|
|
772
|
-
: options.presentation === "centered"
|
|
773
|
-
? {
|
|
774
|
-
kind: "action",
|
|
775
|
-
onPress: () => setCenteredOpen(agentId, !centeredOpenAgents.has(agentId)),
|
|
776
|
-
}
|
|
777
|
-
: {
|
|
778
|
-
kind: "popover",
|
|
779
|
-
Content: PillPopoverContent as ComponentType<any>,
|
|
780
|
-
},
|
|
781
|
-
},
|
|
782
|
-
});
|
|
783
|
-
const handle = typeof registration === "function" ? undefined : registration;
|
|
784
|
-
pills.set(agentId, { dispose: toCleanup(registration), workspaceId, registration: handle });
|
|
785
|
-
// Resolve once so the label is correct even before the probe mounts;
|
|
786
|
-
// ongoing polling is owned by the shared, visibility-gated timer.
|
|
787
|
-
if ((options.resolveLabel || options.resolveIcon) && handle) {
|
|
788
|
-
resolveAndPushLabel(agentId, workspaceId, handle);
|
|
789
|
-
}
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
792
|
-
const cleanup = client.addComposerPill({
|
|
793
|
-
id: options.id,
|
|
794
|
-
title: options.title,
|
|
617
|
+
pills.set(agentId, {
|
|
618
|
+
dispose: toCleanup(registration),
|
|
795
619
|
workspaceId,
|
|
796
|
-
|
|
797
|
-
Component: PillHost,
|
|
798
|
-
onPress() {
|
|
799
|
-
const opener = openers.get(agentId);
|
|
800
|
-
if (opener) {
|
|
801
|
-
const defaultPayload = options.resolveDefaultPayload?.({ agentId, workspaceId });
|
|
802
|
-
opener(defaultPayload);
|
|
803
|
-
}
|
|
804
|
-
},
|
|
620
|
+
registration,
|
|
805
621
|
});
|
|
806
|
-
|
|
622
|
+
// Resolve once so the label is correct even before the icon mounts;
|
|
623
|
+
// ongoing polling is owned by the shared, visibility-gated timer.
|
|
624
|
+
if (options.resolveLabel || options.resolveIcon) {
|
|
625
|
+
resolveAndPushLabel(agentId, workspaceId, registration);
|
|
626
|
+
}
|
|
807
627
|
} catch (error) {
|
|
808
628
|
reportError(agentId, workspaceId, error);
|
|
809
629
|
}
|
|
@@ -816,10 +636,11 @@ export function registerComposerPill<TPayload = any>(
|
|
|
816
636
|
visiblePillMounts.delete(agentId);
|
|
817
637
|
iconValueByAgent.delete(agentId);
|
|
818
638
|
iconListenersByAgent.delete(agentId);
|
|
819
|
-
openers.delete(agentId);
|
|
820
639
|
syncSharedLabelTimer();
|
|
821
640
|
}
|
|
822
641
|
|
|
642
|
+
const lifetime = new AbortController();
|
|
643
|
+
|
|
823
644
|
const unsubscribe = client.paseo.agents.subscribe((update) => {
|
|
824
645
|
if (disposed) return;
|
|
825
646
|
if ("agentId" in update && update.kind === "remove") {
|
|
@@ -833,14 +654,37 @@ export function registerComposerPill<TPayload = any>(
|
|
|
833
654
|
});
|
|
834
655
|
|
|
835
656
|
client.paseo.agents
|
|
836
|
-
.list()
|
|
657
|
+
.list({ subscribe: {}, signal: lifetime.signal })
|
|
837
658
|
.then((result) => {
|
|
838
659
|
if (disposed) return;
|
|
839
|
-
|
|
840
|
-
|
|
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
|
+
});
|
|
841
684
|
}
|
|
842
685
|
})
|
|
843
686
|
.catch((error) => {
|
|
687
|
+
if (lifetime.signal.aborted || disposed) return;
|
|
844
688
|
// Silently dropping this leaves a plugin with zero pills and no clue why.
|
|
845
689
|
reportSuppressed(console, "registerComposerPill agents.list() failed", error, "warn");
|
|
846
690
|
});
|
|
@@ -848,6 +692,7 @@ export function registerComposerPill<TPayload = any>(
|
|
|
848
692
|
return () => {
|
|
849
693
|
if (disposed) return;
|
|
850
694
|
disposed = true;
|
|
695
|
+
lifetime.abort();
|
|
851
696
|
unsubscribe();
|
|
852
697
|
if (sharedLabelTimer) {
|
|
853
698
|
clearInterval(sharedLabelTimer);
|
|
@@ -858,76 +703,11 @@ export function registerComposerPill<TPayload = any>(
|
|
|
858
703
|
visiblePillMounts.clear();
|
|
859
704
|
iconValueByAgent.clear();
|
|
860
705
|
iconListenersByAgent.clear();
|
|
861
|
-
openers.clear();
|
|
862
706
|
};
|
|
863
707
|
}
|
|
864
708
|
|
|
865
|
-
interface DefaultPillBodyProps {
|
|
866
|
-
title: string;
|
|
867
|
-
compactTitle?: string;
|
|
868
|
-
icon?: string;
|
|
869
|
-
compactIcon?: string;
|
|
870
|
-
badgeText?: string;
|
|
871
|
-
compactBadgeText?: string;
|
|
872
|
-
theme: HostPillProps["theme"];
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
function DefaultPillBody({
|
|
876
|
-
title,
|
|
877
|
-
compactTitle,
|
|
878
|
-
icon,
|
|
879
|
-
compactIcon,
|
|
880
|
-
badgeText,
|
|
881
|
-
compactBadgeText,
|
|
882
|
-
theme,
|
|
883
|
-
}: DefaultPillBodyProps) {
|
|
884
|
-
const { Icon } = getClientHost();
|
|
885
|
-
const { isCompact } = useResponsive();
|
|
886
|
-
|
|
887
|
-
const effectiveTitle = isCompact && compactTitle ? compactTitle : title;
|
|
888
|
-
const effectiveIcon = isCompact && compactIcon ? compactIcon : icon;
|
|
889
|
-
const effectiveBadge =
|
|
890
|
-
isCompact && compactBadgeText !== undefined ? compactBadgeText : badgeText;
|
|
891
|
-
|
|
892
|
-
return (
|
|
893
|
-
<View style={styles.pillContainer}>
|
|
894
|
-
{effectiveIcon && <Icon name={effectiveIcon} size={13} color={theme.colors.foreground} />}
|
|
895
|
-
{effectiveTitle ? (
|
|
896
|
-
<Text style={[styles.title, { color: theme.colors.foreground }]}>{effectiveTitle}</Text>
|
|
897
|
-
) : null}
|
|
898
|
-
{effectiveBadge && (
|
|
899
|
-
<View style={[styles.badge, { backgroundColor: theme.colors.surface1 }]}>
|
|
900
|
-
<Text style={[styles.badgeText, { color: theme.colors.foregroundMuted }]}>
|
|
901
|
-
{effectiveBadge}
|
|
902
|
-
</Text>
|
|
903
|
-
</View>
|
|
904
|
-
)}
|
|
905
|
-
</View>
|
|
906
|
-
);
|
|
907
|
-
}
|
|
908
|
-
|
|
909
709
|
const styles = StyleSheet.create({
|
|
910
710
|
popoverContainer: {
|
|
911
711
|
width: "100%",
|
|
912
712
|
},
|
|
913
|
-
pillContainer: {
|
|
914
|
-
flexDirection: "row",
|
|
915
|
-
alignItems: "center",
|
|
916
|
-
gap: 6,
|
|
917
|
-
paddingHorizontal: 8,
|
|
918
|
-
paddingVertical: 3,
|
|
919
|
-
},
|
|
920
|
-
title: {
|
|
921
|
-
fontSize: 12,
|
|
922
|
-
fontWeight: "500",
|
|
923
|
-
},
|
|
924
|
-
badge: {
|
|
925
|
-
borderRadius: 999,
|
|
926
|
-
paddingHorizontal: 5,
|
|
927
|
-
paddingVertical: 1,
|
|
928
|
-
},
|
|
929
|
-
badgeText: {
|
|
930
|
-
fontSize: 10,
|
|
931
|
-
fontWeight: "600",
|
|
932
|
-
},
|
|
933
713
|
});
|