@ggui-ai/mcp-apps-react-native 0.12.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 +4 -4
- package/src/components/UiFeedback.tsx +41 -88
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/
|
|
62
|
-
"@ggui-ai/protocol": "0.
|
|
63
|
-
"@ggui-ai/
|
|
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",
|
|
@@ -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
|
});
|