@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,8 +1,17 @@
1
1
  import React, { type ReactNode } from "react";
2
2
  import { StyleSheet, Text, View, type StyleProp, type ViewStyle } from "react-native";
3
- import { usePluginTheme } from "../theme/provider";
3
+ import { useHostTheme, useHostLayout } from "./theme";
4
+ import { spacing } from "./layout";
4
5
 
5
- export interface DataColumn<T> {
6
+ /**
7
+ * Paseo Plugin Helper — UI data adapters (`paseo-plugin-helper/ui`).
8
+ *
9
+ * Thin, host-delegating data adapters
10
+ * (DataTable). Same contract: host theme colors only, no scroll ownership, no
11
+ * design-system machinery.
12
+ */
13
+
14
+ export interface HostDataColumn<T> {
6
15
  key: string;
7
16
  header: string;
8
17
  flex?: number;
@@ -11,33 +20,33 @@ export interface DataColumn<T> {
11
20
  render: (item: T) => ReactNode;
12
21
  }
13
22
 
14
- export interface DataTableProps<T> {
23
+ export interface HostDataTableProps<T> {
15
24
  data: T[];
16
- columns: DataColumn<T>[];
25
+ columns: HostDataColumn<T>[];
17
26
  keyExtractor: (item: T, index: number) => string;
18
27
  emptyState?: ReactNode;
19
28
  style?: StyleProp<ViewStyle>;
20
29
  }
21
30
 
22
31
  /**
23
- * Responsive data table that automatically reflows between a traditional table
24
- * on desktop and structured card list on mobile / compact viewports.
32
+ * Responsive data table that reflows between a traditional table on wide
33
+ * surfaces and a structured card list on compact/mobile layouts.
25
34
  */
26
- export function DataTable<T>({
35
+ export function HostDataTable<T>({
27
36
  data,
28
37
  columns,
29
38
  keyExtractor,
30
39
  emptyState,
31
40
  style,
32
- }: DataTableProps<T>) {
33
- const { colors, resolveRadius, isCompact } = usePluginTheme();
34
- const radius = resolveRadius("sm");
41
+ }: HostDataTableProps<T>) {
42
+ const { colors } = useHostTheme();
43
+ const layout = useHostLayout();
44
+ const isCompact = layout.compact || layout.platform === "ios" || layout.platform === "android";
35
45
 
36
46
  if (!data || data.length === 0) {
37
47
  return emptyState ? <View style={style}>{emptyState}</View> : null;
38
48
  }
39
49
 
40
- // Mobile / compact card-list layout
41
50
  if (isCompact) {
42
51
  return (
43
52
  <View style={[styles.compactContainer, style]}>
@@ -49,7 +58,6 @@ export function DataTable<T>({
49
58
  {
50
59
  backgroundColor: colors.surface1,
51
60
  borderColor: colors.border,
52
- borderRadius: radius,
53
61
  },
54
62
  ]}
55
63
  >
@@ -67,20 +75,17 @@ export function DataTable<T>({
67
75
  );
68
76
  }
69
77
 
70
- // Desktop tabular layout
71
78
  return (
72
79
  <View
73
80
  style={[
74
81
  styles.table,
75
82
  {
76
83
  borderColor: colors.border,
77
- borderRadius: radius,
78
84
  backgroundColor: colors.surface0,
79
85
  },
80
86
  style,
81
87
  ]}
82
88
  >
83
- {/* Header Row */}
84
89
  <View
85
90
  style={[
86
91
  styles.headerRow,
@@ -100,18 +105,21 @@ export function DataTable<T>({
100
105
  col.align === "right"
101
106
  ? styles.alignRight
102
107
  : col.align === "center"
103
- ? styles.alignCenter
104
- : styles.alignLeft,
108
+ ? styles.alignCenter
109
+ : styles.alignLeft,
105
110
  ]}
106
111
  >
107
- <Text style={[styles.headerText, { color: colors.foregroundMuted }]}>
112
+ <Text
113
+ numberOfLines={1}
114
+ ellipsizeMode="tail"
115
+ style={[styles.headerText, { color: colors.foregroundMuted }]}
116
+ >
108
117
  {col.header}
109
118
  </Text>
110
119
  </View>
111
120
  ))}
112
121
  </View>
113
122
 
114
- {/* Rows */}
115
123
  {data.map((item, idx) => (
116
124
  <View
117
125
  key={keyExtractor(item, idx)}
@@ -130,8 +138,8 @@ export function DataTable<T>({
130
138
  col.align === "right"
131
139
  ? styles.alignRight
132
140
  : col.align === "center"
133
- ? styles.alignCenter
134
- : styles.alignLeft,
141
+ ? styles.alignCenter
142
+ : styles.alignLeft,
135
143
  ]}
136
144
  >
137
145
  {col.render(item)}
@@ -144,24 +152,43 @@ export function DataTable<T>({
144
152
  }
145
153
 
146
154
  const styles = StyleSheet.create({
155
+ compactContainer: {
156
+ gap: spacing.sm,
157
+ },
158
+ compactCard: {
159
+ borderWidth: 1,
160
+ borderRadius: 10,
161
+ padding: spacing.md,
162
+ gap: spacing.xs,
163
+ },
164
+ compactRow: {
165
+ flexDirection: "row",
166
+ justifyContent: "space-between",
167
+ gap: spacing.sm,
168
+ },
169
+ compactHeader: {
170
+ fontSize: 11,
171
+ flexShrink: 1,
172
+ },
173
+ compactValue: {
174
+ flexShrink: 1,
175
+ minWidth: 0,
176
+ },
147
177
  table: {
148
178
  borderWidth: 1,
179
+ borderRadius: 10,
149
180
  overflow: "hidden",
150
181
  },
151
182
  headerRow: {
152
183
  flexDirection: "row",
153
184
  borderBottomWidth: 1,
154
- paddingVertical: 8,
155
- paddingHorizontal: 12,
156
185
  },
157
186
  row: {
158
187
  flexDirection: "row",
159
- paddingVertical: 10,
160
- paddingHorizontal: 12,
161
- alignItems: "center",
162
188
  },
163
189
  cell: {
164
- justifyContent: "center",
190
+ paddingHorizontal: spacing.md,
191
+ paddingVertical: spacing.sm,
165
192
  },
166
193
  alignLeft: {
167
194
  alignItems: "flex-start",
@@ -175,26 +202,7 @@ const styles = StyleSheet.create({
175
202
  headerText: {
176
203
  fontSize: 11,
177
204
  fontWeight: "600",
205
+ letterSpacing: 0.3,
178
206
  textTransform: "uppercase",
179
- letterSpacing: 0.5,
180
- },
181
- compactContainer: {
182
- gap: 8,
183
- },
184
- compactCard: {
185
- borderWidth: 1,
186
- padding: 12,
187
- gap: 6,
188
- },
189
- compactRow: {
190
- flexDirection: "row",
191
- justifyContent: "space-between",
192
- alignItems: "center",
193
- },
194
- compactHeader: {
195
- fontSize: 12,
196
- },
197
- compactValue: {
198
- alignItems: "flex-end",
199
207
  },
200
208
  });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Paseo Plugin Helper — UI Kit / Adapters (`paseo-plugin-helper/ui`).
3
+ *
4
+ * Thin, composable adapters strictly conforming to upstream native
5
+ * primitives. Rules for everything in this entry:
6
+ *
7
+ * - Scroll ownership stays with the host (`Modal.Content` default
8
+ * `scrollable`; explicit host `ScrollView` only where the host supplies
9
+ * no scroller). Never force `scrollable={false}` plus a helper-owned
10
+ * replacement scroller. No scroll-ownership state machine.
11
+ * - No artificial width caps: no `maxContentWidth`, no `size="large"` with
12
+ * hardcoded `minWidth`. Fill the host-allocated frame fluidly.
13
+ * - No DOM CSS variable scrapers: colors arrive via the host `theme` prop
14
+ * through `HostThemeProvider` — never from `getComputedStyle`.
15
+ * - Upstream settings primitives come from the host bundle
16
+ * (`@getpaseo/plugin/client/ui`), injected by the plugin — never
17
+ * re-implemented here.
18
+ *
19
+ * This is the only UI the package ships: the legacy bespoke kit was removed
20
+ * in #938, and the CLI audit flags new `maxContentWidth` / `scrollable={false}`
21
+ * uses with pointers here.
22
+ *
23
+ * Module map:
24
+ * - `ui/theme.tsx` — `HostThemeProvider` / `useHostTheme` (host colors only)
25
+ * - `ui/color.ts` — `alpha()` and status color helpers (pure)
26
+ * - `ui/modal.tsx` — `HostModalContent` / `HostScroll` / `HostModalSection`
27
+ * - `ui/layout.tsx` — `HostStack` / `HostRow` / `HostGrid` / `HostActionBar` / `HostFormRow`
28
+ * - `ui/content.tsx` — `HostCard` / `HostTabs` / `HostBadge` / `HostKeyValue` / …
29
+ * - `ui/controls.tsx` — `HostButton` / `HostToggle` / `HostSelect` / `HostTextInput` / …
30
+ * - `ui/text.tsx` — `HostCodeBlock` / `HostCommandBox` / `HostTruncatedText` / …
31
+ * - `ui/data.tsx` — `HostDataTable`
32
+ * - `ui/settings.ts` — settings screen adapters over the host ui bundle
33
+ */
34
+
35
+ export * from "./theme";
36
+ export * from "./color";
37
+ export * from "./modal";
38
+ export * from "./layout";
39
+ export * from "./content";
40
+ export * from "./controls";
41
+ export * from "./text";
42
+ export * from "./data";
43
+ export * from "./settings";
44
+ export { PluginReloadBanner } from "./plugin-reload-banner";
@@ -0,0 +1,263 @@
1
+ import React, { type ReactNode } from "react";
2
+ import { StyleSheet, Text, View, type StyleProp, type ViewStyle } from "react-native";
3
+ import { useHostTheme } from "./theme";
4
+
5
+ /** Static spacing scale (px). The host owns the design language; this is just layout. */
6
+ export const spacing = {
7
+ xs: 4,
8
+ sm: 8,
9
+ md: 12,
10
+ lg: 16,
11
+ xl: 24,
12
+ } as const;
13
+
14
+ export type SpacingValue = keyof typeof spacing | number;
15
+
16
+ export function resolveSpacing(value: SpacingValue | undefined, fallback: number): number {
17
+ if (value === undefined) return fallback;
18
+ if (typeof value === "number") return value;
19
+ return spacing[value] ?? fallback;
20
+ }
21
+
22
+ export interface HostRowProps {
23
+ children: ReactNode;
24
+ gap?: number;
25
+ align?: "start" | "center" | "end" | "stretch";
26
+ justify?: "start" | "center" | "end" | "between";
27
+ wrap?: boolean;
28
+ style?: StyleProp<ViewStyle>;
29
+ }
30
+
31
+ /**
32
+ * Host-delegating horizontal row for `paseo-plugin-helper/ui`.
33
+ *
34
+ * Colors come from `useHostTheme()` (provided by `HostThemeProvider`) — no
35
+ * theme scraping.
36
+ */
37
+ export function HostRow({
38
+ children,
39
+ gap = 8,
40
+ align = "center",
41
+ justify = "start",
42
+ wrap = false,
43
+ style,
44
+ }: HostRowProps) {
45
+ return (
46
+ <View
47
+ style={[
48
+ styles.row,
49
+ {
50
+ gap,
51
+ alignItems: alignMap[align],
52
+ justifyContent: justifyMap[justify],
53
+ flexWrap: wrap ? "wrap" : "nowrap",
54
+ },
55
+ style,
56
+ ]}
57
+ >
58
+ {children}
59
+ </View>
60
+ );
61
+ }
62
+
63
+ export interface HostStackProps {
64
+ children: ReactNode;
65
+ gap?: number;
66
+ align?: "start" | "center" | "end" | "stretch";
67
+ justify?: "start" | "center" | "end" | "between";
68
+ grow?: boolean;
69
+ style?: StyleProp<ViewStyle>;
70
+ }
71
+
72
+ /**
73
+ * Host-delegating vertical stack for `paseo-plugin-helper/ui`.
74
+ *
75
+ * Colors come from `useHostTheme()` (provided by `HostThemeProvider`) — no
76
+ * theme scraping.
77
+ */
78
+ export function HostStack({
79
+ children,
80
+ gap = 8,
81
+ align = "stretch",
82
+ justify = "start",
83
+ grow = false,
84
+ style,
85
+ }: HostStackProps) {
86
+ return (
87
+ <View
88
+ style={[
89
+ styles.stack,
90
+ {
91
+ gap,
92
+ alignItems: alignMap[align],
93
+ justifyContent: justifyMap[justify],
94
+ ...(grow ? { flex: 1, minHeight: 0 } : null),
95
+ },
96
+ style,
97
+ ]}
98
+ >
99
+ {children}
100
+ </View>
101
+ );
102
+ }
103
+
104
+ export interface HostGridProps {
105
+ children: ReactNode;
106
+ columns?: number;
107
+ gap?: number;
108
+ style?: StyleProp<ViewStyle>;
109
+ }
110
+
111
+ /**
112
+ * Host-delegating wrapping grid for `paseo-plugin-helper/ui`.
113
+ *
114
+ * Colors come from `useHostTheme()` (provided by `HostThemeProvider`) — no
115
+ * theme scraping.
116
+ */
117
+ export function HostGrid({
118
+ children,
119
+ columns = 2,
120
+ gap = 8,
121
+ style,
122
+ }: HostGridProps) {
123
+ return (
124
+ <View style={[styles.grid, { gap }, style]}>
125
+ {React.Children.map(children, (child) => (
126
+ <View style={{ flexBasis: `${100 / columns}%`, flexGrow: 0, flexShrink: 1, minWidth: 0 }}>
127
+ {child}
128
+ </View>
129
+ ))}
130
+ </View>
131
+ );
132
+ }
133
+
134
+ export interface HostActionBarProps {
135
+ children: ReactNode;
136
+ style?: StyleProp<ViewStyle>;
137
+ }
138
+
139
+ /**
140
+ * Host-delegating action bar for `paseo-plugin-helper/ui`.
141
+ *
142
+ * Colors come from `useHostTheme()` (provided by `HostThemeProvider`) — no
143
+ * theme scraping.
144
+ */
145
+ export function HostActionBar({
146
+ children,
147
+ style,
148
+ }: HostActionBarProps) {
149
+ return (
150
+ <View style={[styles.actionBar, style]}>
151
+ {children}
152
+ </View>
153
+ );
154
+ }
155
+
156
+ export interface HostFormRowProps {
157
+ label: string;
158
+ description?: string;
159
+ children: ReactNode;
160
+ layout?: "stacked" | "inline";
161
+ style?: StyleProp<ViewStyle>;
162
+ }
163
+
164
+ /**
165
+ * Host-delegating form row for `paseo-plugin-helper/ui`.
166
+ *
167
+ * Colors come from `useHostTheme()` (provided by `HostThemeProvider`) — no
168
+ * theme scraping.
169
+ */
170
+ export function HostFormRow({
171
+ label,
172
+ description,
173
+ children,
174
+ layout = "stacked",
175
+ style,
176
+ }: HostFormRowProps) {
177
+ const { colors } = useHostTheme();
178
+
179
+ return (
180
+ <View
181
+ style={[
182
+ styles.formRow,
183
+ layout === "inline" ? styles.formRowInline : styles.formRowStacked,
184
+ style,
185
+ ]}
186
+ >
187
+ <View style={styles.formLabel}>
188
+ <Text style={[styles.formLabelText, { color: colors.foreground }]}>{label}</Text>
189
+ {description ? (
190
+ <Text style={[styles.formDescription, { color: colors.foregroundMuted }]}>
191
+ {description}
192
+ </Text>
193
+ ) : null}
194
+ </View>
195
+ <View style={styles.formControl}>{children}</View>
196
+ </View>
197
+ );
198
+ }
199
+
200
+ const alignMap = {
201
+ start: "flex-start",
202
+ center: "center",
203
+ end: "flex-end",
204
+ stretch: "stretch",
205
+ } as const;
206
+
207
+ const justifyMap = {
208
+ start: "flex-start",
209
+ center: "center",
210
+ end: "flex-end",
211
+ between: "space-between",
212
+ } as const;
213
+
214
+ const styles = StyleSheet.create({
215
+ row: {
216
+ flexDirection: "row",
217
+ width: "100%",
218
+ },
219
+ stack: {
220
+ flexDirection: "column",
221
+ width: "100%",
222
+ },
223
+ grid: {
224
+ flexDirection: "row",
225
+ flexWrap: "wrap",
226
+ width: "100%",
227
+ },
228
+ actionBar: {
229
+ flexDirection: "row",
230
+ alignItems: "center",
231
+ justifyContent: "flex-end",
232
+ gap: 8,
233
+ width: "100%",
234
+ flexWrap: "wrap",
235
+ },
236
+ formRow: {
237
+ gap: 4,
238
+ width: "100%",
239
+ },
240
+ formRowStacked: {
241
+ flexDirection: "column",
242
+ },
243
+ formRowInline: {
244
+ flexDirection: "row",
245
+ alignItems: "center",
246
+ justifyContent: "space-between",
247
+ },
248
+ formLabel: {
249
+ gap: 2,
250
+ flexShrink: 1,
251
+ },
252
+ formLabelText: {
253
+ fontSize: 13,
254
+ fontWeight: "600",
255
+ },
256
+ formDescription: {
257
+ fontSize: 11,
258
+ },
259
+ formControl: {
260
+ flexShrink: 1,
261
+ minWidth: 0,
262
+ },
263
+ });
@@ -0,0 +1,111 @@
1
+ import React, { type ReactNode } from "react";
2
+ import {
3
+ ScrollView as FallbackScrollView,
4
+ StyleSheet,
5
+ View,
6
+ type ScrollViewProps,
7
+ type StyleProp,
8
+ type ViewStyle,
9
+ } from "react-native";
10
+ import {
11
+ getClientHost,
12
+ getOptionalClientHost,
13
+ selectHostScrollView,
14
+ type HostScrollView,
15
+ } from "../core/host";
16
+
17
+ /**
18
+ * Host-delegating modal content for `paseo-plugin-helper/ui`.
19
+ *
20
+ * Contract — ONLY for content rendered directly inside your OWN host
21
+ * `<Modal>` (e.g. x-comms style own-modal surfaces):
22
+ * - Scroll ownership stays with the host: renders the injected host
23
+ * `<Modal.Content>` with its default `scrollable` behavior and adds NO
24
+ * helper-owned scroller, so sheet gestures, keyboard avoidance, and
25
+ * safe-area clearance keep working on compact/mobile and desktop alike.
26
+ * - No width opinions: there is deliberately NO `maxContentWidth` /
27
+ * `size="large"` prop. The host allocates the dialog frame; this wrapper
28
+ * fills it fluidly (`width: "100%"`).
29
+ * - No theme scraping: colors come from the host `theme` prop via
30
+ * `PluginThemeProvider`, never from DOM CSS variables.
31
+ * - NEVER render this inside `registerComposerPill` `renderModal`: the pill
32
+ * host already provides the one `<Modal.Content>` (or no modal at all on
33
+ * 0.8 popovers), so a nested `<Modal.Content>` violates the host contract
34
+ * and crashes the plugin. Use `HostModalSection` there instead.
35
+ */
36
+ export function HostModalContent({
37
+ children,
38
+ style,
39
+ contentContainerStyle,
40
+ }: {
41
+ children: ReactNode;
42
+ style?: StyleProp<ViewStyle>;
43
+ contentContainerStyle?: StyleProp<ViewStyle>;
44
+ }) {
45
+ const { Modal } = getClientHost();
46
+ return (
47
+ <Modal.Content>
48
+ <View style={[styles.fluid, style, contentContainerStyle]}>{children}</View>
49
+ </Modal.Content>
50
+ );
51
+ }
52
+
53
+ /**
54
+ * Explicit single scroll owner for surfaces where the host supplies NO
55
+ * scroller (sidebar surfaces, settings screens). Prefers the injected host
56
+ * ScrollView (sheet-gesture integrated on Paseo v0.8) and falls back to plain
57
+ * React Native ScrollView. Callers own the decision to scroll; this component
58
+ * never nests itself inside another scroller.
59
+ */
60
+ export function HostScroll({
61
+ children,
62
+ style,
63
+ contentContainerStyle,
64
+ ...props
65
+ }: {
66
+ children: ReactNode;
67
+ style?: StyleProp<ViewStyle>;
68
+ contentContainerStyle?: StyleProp<ViewStyle>;
69
+ } & ScrollViewProps) {
70
+ const ResolvedScrollView = selectHostScrollView(
71
+ getOptionalClientHost(),
72
+ FallbackScrollView as unknown as HostScrollView,
73
+ );
74
+ return (
75
+ <ResolvedScrollView
76
+ {...(props as ScrollViewProps)}
77
+ style={[styles.fluid, style]}
78
+ contentContainerStyle={contentContainerStyle}
79
+ >
80
+ {children}
81
+ </ResolvedScrollView>
82
+ );
83
+ }
84
+
85
+ const styles = StyleSheet.create({
86
+ fluid: {
87
+ width: "100%",
88
+ },
89
+ });
90
+
91
+ /**
92
+ * Fluid inner content for pill-embedded modals (`registerComposerPill`
93
+ * `renderModal`).
94
+ *
95
+ * The pill host already provides the one `<Modal.Content>` (legacy/centered
96
+ * modal paths) or no modal at all (0.8 popover path, where the host owns the
97
+ * outer scroll). This renders a plain fluid `<View>` — no `<Modal.Content>`,
98
+ * no scroller, no width caps — so exactly one `<Modal.Content>` exists and
99
+ * nothing nests. Pair with `hostScroll: true` on the pill registration so
100
+ * the wrapper leaves scrolling to the host; without it the wrapper bounds
101
+ * the dialog (`scrollable={false}`) for legacy `ModalBody` content.
102
+ */
103
+ export function HostModalSection({
104
+ children,
105
+ style,
106
+ }: {
107
+ children: ReactNode;
108
+ style?: StyleProp<ViewStyle>;
109
+ }) {
110
+ return <View style={[styles.fluid, style]}>{children}</View>;
111
+ }