@ggui-ai/mcp-apps-react-native 0.11.0 → 0.13.0
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/mcp-apps-react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "React Native SDK for ggui — components and hooks for embedding agent-generated UIs in mobile apps.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ggui",
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@modelcontextprotocol/ext-apps": "^1.7.2",
|
|
61
|
-
"@ggui-ai/design": "0.
|
|
62
|
-
"@ggui-ai/protocol": "0.
|
|
63
|
-
"@ggui-ai/iframe-runtime": "0.
|
|
61
|
+
"@ggui-ai/design": "0.13.0",
|
|
62
|
+
"@ggui-ai/protocol": "0.13.0",
|
|
63
|
+
"@ggui-ai/iframe-runtime": "0.13.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
@@ -164,7 +164,12 @@ export async function dispatchHostBridgeRequest(
|
|
|
164
164
|
// `define`), and a hand-maintained literal would silently drift
|
|
165
165
|
// from the published version at every release.
|
|
166
166
|
hostInfo: { name: 'ggui-react-native', version: 'unstamped' },
|
|
167
|
-
|
|
167
|
+
// Truthful positive advertisement (kit R2 grade, ggui#600 OPEN
|
|
168
|
+
// FINDING closed): a wired tools/call relay advertises
|
|
169
|
+
// `serverTools` so the runtime never spends a failed user
|
|
170
|
+
// gesture probing a capability the host actually has.
|
|
171
|
+
hostCapabilities:
|
|
172
|
+
ctx.onToolCall !== undefined ? { serverTools: {} } : {},
|
|
168
173
|
hostContext: {
|
|
169
174
|
locale: ctx.locale,
|
|
170
175
|
containerDimensions: ctx.containerDimensions,
|
|
@@ -3,28 +3,32 @@
|
|
|
3
3
|
* shell chrome ("did this generated UI work for you?").
|
|
4
4
|
*
|
|
5
5
|
* Platform delta (vs `ggui-react/src/components/UiFeedback.tsx`):
|
|
6
|
-
* - DOM elements (`div` / `button`
|
|
7
|
-
*
|
|
6
|
+
* - DOM elements (`div` / `button`) become RN primitives
|
|
7
|
+
* (`View` / `Pressable` / `Text`).
|
|
8
8
|
* - Inline styles with `--ggui-*` CSS-variable hooks become
|
|
9
9
|
* `StyleSheet.create` constants carrying the same neutral palette
|
|
10
10
|
* the web copy uses as its variable fallbacks (RN has no CSS
|
|
11
11
|
* variable layer).
|
|
12
12
|
* - `data-ggui-ui-feedback*` styling/test hooks become `testID`s
|
|
13
13
|
* (`ggui-ui-feedback`, `ggui-ui-feedback-verdict-<verdict>`, …).
|
|
14
|
-
* - The comment `<form>` submit becomes a Send `Pressable` plus the
|
|
15
|
-
* `TextInput`'s `onSubmitEditing` (keyboard return key).
|
|
16
14
|
* - `role` / `aria-label` become `accessibilityLabel` /
|
|
17
15
|
* `accessibilityRole` props.
|
|
16
|
+
* - The web copy's monochrome stroked SVG thumbs become 👍 / 👎
|
|
17
|
+
* emoji glyphs in `Text` (#653 lean call: `react-native-svg` is
|
|
18
|
+
* not a dependency of this package and one chrome row does not
|
|
19
|
+
* justify adding it to the lockstep wave; the glyph renders in the
|
|
20
|
+
* platform's emoji face rather than the theme ink — revisit if a
|
|
21
|
+
* ggui icon dependency ever lands for other reasons).
|
|
18
22
|
* The exported surface is identical to the web copy — pinned by the
|
|
19
23
|
* twin-parity gate (`DOCUMENTED_DELTA_TWINS`).
|
|
20
24
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* Two verdicts — thumbs up / thumbs down (#653) — plus a dismiss
|
|
26
|
+
* control. Entirely host-driven: the component renders NOTHING unless
|
|
27
|
+
* the host passes `onUiFeedback`, so deployments that don't collect
|
|
28
|
+
* feedback never show a dead affordance. Where the payload goes is
|
|
29
|
+
* the host's choice — a logger, an analytics client, a support inbox;
|
|
30
|
+
* this component only builds the typed {@link UiFeedbackPayload} and
|
|
31
|
+
* hands it over.
|
|
28
32
|
*
|
|
29
33
|
* Zero wire surface: feedback never crosses the agent ↔ UI contract.
|
|
30
34
|
* It is host-app chrome (Data vs Behavior — the agent cannot observe
|
|
@@ -50,20 +54,17 @@
|
|
|
50
54
|
* choice safe: omitting `onUiFeedback` fully disables this surface.
|
|
51
55
|
*/
|
|
52
56
|
import { useCallback, useState } from 'react';
|
|
53
|
-
import { Pressable, StyleSheet, Text,
|
|
57
|
+
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
|
54
58
|
|
|
55
|
-
/** The
|
|
56
|
-
export type UiFeedbackVerdict = '
|
|
59
|
+
/** The two feedback verdicts the affordance can emit. */
|
|
60
|
+
export type UiFeedbackVerdict = 'up' | 'down';
|
|
57
61
|
|
|
58
62
|
/**
|
|
59
63
|
* Payload handed to {@link UiFeedbackProps.onUiFeedback}. Context
|
|
60
|
-
* fields are present exactly when the host supplied them as props
|
|
61
|
-
* `comment` is present only for `verdict: 'other'` with a non-empty
|
|
62
|
-
* trimmed comment.
|
|
64
|
+
* fields are present exactly when the host supplied them as props.
|
|
63
65
|
*/
|
|
64
66
|
export interface UiFeedbackPayload {
|
|
65
67
|
verdict: UiFeedbackVerdict;
|
|
66
|
-
comment?: string;
|
|
67
68
|
/** GguiSession id of the render the feedback is about. */
|
|
68
69
|
sessionId?: string;
|
|
69
70
|
/** Tool that produced the render (e.g. `ggui_render`). */
|
|
@@ -82,19 +83,26 @@ export interface UiFeedbackProps {
|
|
|
82
83
|
toolName?: string;
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
type Phase = 'idle' | '
|
|
86
|
+
type Phase = 'idle' | 'sent' | 'dismissed';
|
|
87
|
+
|
|
88
|
+
const VERDICT_GLYPHS: Record<UiFeedbackVerdict, string> = {
|
|
89
|
+
up: '👍',
|
|
90
|
+
down: '👎',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const VERDICT_LABELS: Record<UiFeedbackVerdict, string> = {
|
|
94
|
+
up: 'Thumbs up',
|
|
95
|
+
down: 'Thumbs down',
|
|
96
|
+
};
|
|
86
97
|
|
|
87
98
|
export function UiFeedback({ onUiFeedback, sessionId, toolName }: UiFeedbackProps) {
|
|
88
99
|
const [phase, setPhase] = useState<Phase>('idle');
|
|
89
|
-
const [comment, setComment] = useState('');
|
|
90
100
|
|
|
91
101
|
const emit = useCallback(
|
|
92
|
-
(verdict: UiFeedbackVerdict
|
|
102
|
+
(verdict: UiFeedbackVerdict) => {
|
|
93
103
|
if (!onUiFeedback) return;
|
|
94
|
-
const trimmed = commentText?.trim();
|
|
95
104
|
onUiFeedback({
|
|
96
105
|
verdict,
|
|
97
|
-
...(trimmed !== undefined && trimmed.length > 0 ? { comment: trimmed } : {}),
|
|
98
106
|
...(sessionId !== undefined ? { sessionId } : {}),
|
|
99
107
|
...(toolName !== undefined ? { toolName } : {}),
|
|
100
108
|
});
|
|
@@ -103,10 +111,6 @@ export function UiFeedback({ onUiFeedback, sessionId, toolName }: UiFeedbackProp
|
|
|
103
111
|
[onUiFeedback, sessionId, toolName],
|
|
104
112
|
);
|
|
105
113
|
|
|
106
|
-
const onCommentSubmit = useCallback(() => {
|
|
107
|
-
emit('other', comment);
|
|
108
|
-
}, [emit, comment]);
|
|
109
|
-
|
|
110
114
|
// Hidden entirely: no sink wired, or the user dismissed it.
|
|
111
115
|
if (!onUiFeedback || phase === 'dismissed') return null;
|
|
112
116
|
|
|
@@ -120,54 +124,19 @@ export function UiFeedback({ onUiFeedback, sessionId, toolName }: UiFeedbackProp
|
|
|
120
124
|
<Text testID="ggui-ui-feedback-thanks" style={styles.thanksText}>
|
|
121
125
|
Thanks for the feedback
|
|
122
126
|
</Text>
|
|
123
|
-
) : phase === 'comment' ? (
|
|
124
|
-
<View style={styles.commentForm} testID="ggui-ui-feedback-comment-form">
|
|
125
|
-
<TextInput
|
|
126
|
-
value={comment}
|
|
127
|
-
onChangeText={setComment}
|
|
128
|
-
onSubmitEditing={onCommentSubmit}
|
|
129
|
-
placeholder="What happened?"
|
|
130
|
-
accessibilityLabel="Feedback comment"
|
|
131
|
-
testID="ggui-ui-feedback-comment"
|
|
132
|
-
style={styles.commentInput}
|
|
133
|
-
autoFocus
|
|
134
|
-
/>
|
|
135
|
-
<Pressable
|
|
136
|
-
accessibilityRole="button"
|
|
137
|
-
style={styles.verdictButton}
|
|
138
|
-
testID="ggui-ui-feedback-send"
|
|
139
|
-
onPress={onCommentSubmit}
|
|
140
|
-
>
|
|
141
|
-
<Text style={styles.verdictText}>Send</Text>
|
|
142
|
-
</Pressable>
|
|
143
|
-
</View>
|
|
144
127
|
) : (
|
|
145
|
-
|
|
128
|
+
(['up', 'down'] as const).map((verdict) => (
|
|
146
129
|
<Pressable
|
|
130
|
+
key={verdict}
|
|
147
131
|
accessibilityRole="button"
|
|
132
|
+
accessibilityLabel={VERDICT_LABELS[verdict]}
|
|
148
133
|
style={styles.verdictButton}
|
|
149
|
-
testID=
|
|
150
|
-
onPress={() => emit(
|
|
134
|
+
testID={`ggui-ui-feedback-verdict-${verdict}`}
|
|
135
|
+
onPress={() => emit(verdict)}
|
|
151
136
|
>
|
|
152
|
-
<Text style={styles.verdictText}>
|
|
137
|
+
<Text style={styles.verdictText}>{VERDICT_GLYPHS[verdict]}</Text>
|
|
153
138
|
</Pressable>
|
|
154
|
-
|
|
155
|
-
accessibilityRole="button"
|
|
156
|
-
style={styles.verdictButton}
|
|
157
|
-
testID="ggui-ui-feedback-verdict-dislike"
|
|
158
|
-
onPress={() => emit('dislike')}
|
|
159
|
-
>
|
|
160
|
-
<Text style={styles.verdictText}>Dislike</Text>
|
|
161
|
-
</Pressable>
|
|
162
|
-
<Pressable
|
|
163
|
-
accessibilityRole="button"
|
|
164
|
-
style={styles.verdictButton}
|
|
165
|
-
testID="ggui-ui-feedback-verdict-other"
|
|
166
|
-
onPress={() => setPhase('comment')}
|
|
167
|
-
>
|
|
168
|
-
<Text style={styles.verdictText}>Other…</Text>
|
|
169
|
-
</Pressable>
|
|
170
|
-
</>
|
|
139
|
+
))
|
|
171
140
|
)}
|
|
172
141
|
<Pressable
|
|
173
142
|
accessibilityRole="button"
|
|
@@ -199,12 +168,12 @@ const styles = StyleSheet.create({
|
|
|
199
168
|
borderWidth: 1,
|
|
200
169
|
borderColor: '#e5e7eb',
|
|
201
170
|
borderRadius: 4,
|
|
202
|
-
paddingVertical:
|
|
203
|
-
paddingHorizontal:
|
|
171
|
+
paddingVertical: 3,
|
|
172
|
+
paddingHorizontal: 7,
|
|
204
173
|
},
|
|
205
174
|
verdictText: {
|
|
206
175
|
fontSize: 12,
|
|
207
|
-
|
|
176
|
+
lineHeight: 14,
|
|
208
177
|
},
|
|
209
178
|
dismissButton: {
|
|
210
179
|
paddingVertical: 2,
|
|
@@ -214,20 +183,4 @@ const styles = StyleSheet.create({
|
|
|
214
183
|
fontSize: 12,
|
|
215
184
|
color: '#9ca3af',
|
|
216
185
|
},
|
|
217
|
-
commentForm: {
|
|
218
|
-
flexDirection: 'row',
|
|
219
|
-
alignItems: 'center',
|
|
220
|
-
gap: 4,
|
|
221
|
-
},
|
|
222
|
-
commentInput: {
|
|
223
|
-
fontSize: 12,
|
|
224
|
-
paddingVertical: 2,
|
|
225
|
-
paddingHorizontal: 6,
|
|
226
|
-
minWidth: 140,
|
|
227
|
-
borderWidth: 1,
|
|
228
|
-
borderColor: '#e5e7eb',
|
|
229
|
-
borderRadius: 4,
|
|
230
|
-
backgroundColor: '#ffffff',
|
|
231
|
-
color: '#1f2937',
|
|
232
|
-
},
|
|
233
186
|
});
|
|
@@ -93,9 +93,33 @@ export interface ThemeProviderProps {
|
|
|
93
93
|
children: ReactNode;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
/**
|
|
97
|
+
* TERMINAL CONSUMER of the theme-mode ladder (adversarial-cycle
|
|
98
|
+
* ruling, ggui#598 leg 4): the RN provider paints the NATIVE chrome
|
|
99
|
+
* around the WebView — there is no downstream layer left to fill an
|
|
100
|
+
* absent opinion, and React Native must render with SOME concrete
|
|
101
|
+
* scheme. The `?? 'light'` here is therefore the documented
|
|
102
|
+
* terminal-consumer default (the same posture as the design package's
|
|
103
|
+
* css-tokens terminal default), NOT a mid-ladder defaulting: it fires
|
|
104
|
+
* only after the embedder's explicit prop AND the OS scheme are both
|
|
105
|
+
* absent (`useColorScheme()` → null, rare). Mid-ladder code — anything
|
|
106
|
+
* with a layer below it — must never default; it delegates to
|
|
107
|
+
* `@ggui-ai/protocol/integrations/theme-binding` and lets absence
|
|
108
|
+
* propagate (the composition law).
|
|
109
|
+
*
|
|
110
|
+
* @internal — exported for the precedence pin; production caller is
|
|
111
|
+
* `ThemeProvider`.
|
|
112
|
+
*/
|
|
113
|
+
export function resolveNativeScheme(
|
|
114
|
+
prop: 'light' | 'dark' | undefined,
|
|
115
|
+
system: 'light' | 'dark' | null | undefined,
|
|
116
|
+
): 'light' | 'dark' {
|
|
117
|
+
return prop ?? system ?? 'light';
|
|
118
|
+
}
|
|
119
|
+
|
|
96
120
|
export function ThemeProvider({ colorScheme: colorSchemeProp, children }: ThemeProviderProps) {
|
|
97
121
|
const systemScheme = useColorScheme();
|
|
98
|
-
const effectiveScheme = colorSchemeProp
|
|
122
|
+
const effectiveScheme = resolveNativeScheme(colorSchemeProp, systemScheme);
|
|
99
123
|
const [reduceMotionEnabled, setReduceMotionEnabled] = useState(false);
|
|
100
124
|
|
|
101
125
|
useEffect(() => {
|