@farmslot/adapter-rn 0.28.0 → 0.29.1

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/CHANGELOG.md CHANGED
@@ -6,6 +6,14 @@ All notable changes to `@farmslot/adapter-rn` (published as `@farmslot/expo-reci
6
6
 
7
7
  - Active-development baseline; add user-facing changes here before release or package publication.
8
8
 
9
+ ## 0.29.1 - 2026-10-21
10
+
11
+ - Align with `@farmslot/protocol` 0.61.0, `@farmslot/recipe-runner` 0.46.1, `@farmslot/agent-runtime` 0.37.0.
12
+
13
+ ## 0.29.0 - 2026-10-20
14
+
15
+ - The native recipe HUD drops the default RUN badge so the two-line intent gets the full width at 13px/17px, and adds a tiny muted row with the step that just finished (`✓`/`✗`) and `N/M`, within the unchanged 360 by 88 panel. A caller-supplied `text.badge` still renders.
16
+
9
17
  ## 0.28.0 - 2026-10-19
10
18
 
11
19
  - Align with `@farmslot/protocol` 0.59.0, `@farmslot/recipe-runner` 0.45.0 and `@farmslot/agent-runtime` 0.35.0.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farmslot/adapter-rn",
3
- "version": "0.28.0",
3
+ "version": "0.29.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -46,9 +46,9 @@
46
46
  "prepublishOnly": "node ../../scripts/quality/check-farmslot-package-readiness.mjs --publish --packages @farmslot/adapter-rn"
47
47
  },
48
48
  "dependencies": {
49
- "@farmslot/agent-runtime": "0.35.0",
50
- "@farmslot/protocol": "0.59.0",
51
- "@farmslot/recipe-runner": "0.45.0",
49
+ "@farmslot/agent-runtime": "0.37.0",
50
+ "@farmslot/protocol": "0.61.0",
51
+ "@farmslot/recipe-runner": "0.46.1",
52
52
  "pngjs": "^7.0.0",
53
53
  "ws": "^8.18.0"
54
54
  },
@@ -11,7 +11,16 @@ import {
11
11
  import { initialWindowMetrics, SafeAreaInsetsContext } from 'react-native-safe-area-context';
12
12
 
13
13
  import {
14
+ HUD_COLORS,
15
+ HUD_NATIVE_FINISHED_FONT_SIZE,
16
+ HUD_NATIVE_FINISHED_LINE_HEIGHT,
17
+ HUD_NATIVE_INTENT_LINE_HEIGHT,
18
+ HUD_NATIVE_INTENT_MAX_LINES,
19
+ HUD_PANEL_BOUNDS,
20
+ hudFinishedLine,
14
21
  hudIntentDisplay,
22
+ type HudStepResult,
23
+ nextHudFinishedStep,
15
24
  type RecipeHudPlacement,
16
25
  waitForHudPaint,
17
26
  } from '@farmslot/protocol/recipe';
@@ -25,6 +34,8 @@ export interface FarmslotRecipeHudState {
25
34
  totalSteps?: number;
26
35
  error?: string;
27
36
  position?: RecipeHudPlacement;
37
+ /** The step that just finished; useRecipeHudState keeps it across updates. */
38
+ finished?: Pick<HudStepResult, 'intent' | 'status'>;
28
39
  }
29
40
 
30
41
  export type RecipeHudStateController = readonly [
@@ -37,18 +48,28 @@ interface HudAcknowledgement {
37
48
  reject(error: unknown): void;
38
49
  }
39
50
 
40
- /** Bridge acknowledgement follows the committed HUD rather than the state setter. */
51
+ /**
52
+ * Bridge acknowledgement follows the committed HUD rather than the state setter.
53
+ * Updates carry the step that just finished, reset at each new run.
54
+ */
41
55
  export function useRecipeHudState(): RecipeHudStateController {
42
56
  const [state, setState] = useState<FarmslotRecipeHudState | null>(null);
43
- const current = useRef(state);
44
- current.current = state;
57
+ const received = useRef<FarmslotRecipeHudState | null>(null);
58
+ const lastResult = useRef<HudStepResult | null>(null);
45
59
  const pending = useRef<HudAcknowledgement[]>([]);
46
60
  const lifecycle = useRef<AbortController | null>(null);
47
61
  const update = useCallback((next: FarmslotRecipeHudState | null) => {
48
- if (next === current.current) return Promise.resolve();
62
+ if (next === received.current) return Promise.resolve();
63
+ received.current = next;
64
+ const step = nextHudFinishedStep(lastResult.current, next);
65
+ lastResult.current = step.last;
66
+ const shown =
67
+ next && next.finished === undefined && step.finished
68
+ ? { ...next, finished: { intent: step.finished.intent, status: step.finished.status } }
69
+ : next;
49
70
  return new Promise<void>((resolve, reject) => {
50
71
  pending.current.push({ resolve, reject });
51
- setState(next);
72
+ setState(shown);
52
73
  });
53
74
  }, []);
54
75
  useEffect(() => {
@@ -108,6 +129,17 @@ export interface RecipeHudProps extends RecipeHudOptions {
108
129
  state: FarmslotRecipeHudState | null;
109
130
  }
110
131
 
132
+ const PADDING_VERTICAL = 3;
133
+ const ERROR_LINE_HEIGHT = 16;
134
+ // The native panel never grows: the finished row shows only while two intent
135
+ // lines, that row and an error line still fit its height.
136
+ const FINISHED_ROW_FITS =
137
+ PADDING_VERTICAL * 2 +
138
+ HUD_NATIVE_INTENT_LINE_HEIGHT * HUD_NATIVE_INTENT_MAX_LINES +
139
+ HUD_NATIVE_FINISHED_LINE_HEIGHT +
140
+ ERROR_LINE_HEIGHT <=
141
+ HUD_PANEL_BOUNDS.native.maxHeight;
142
+
111
143
  export function RecipeHud({
112
144
  state,
113
145
  styles: customStyles,
@@ -117,10 +149,15 @@ export function RecipeHud({
117
149
  }: Readonly<RecipeHudProps>): React.ReactElement | null {
118
150
  const insets = useContext(SafeAreaInsetsContext) ?? initialWindowMetrics?.insets;
119
151
  if (!state || state.status === 'idle') return null;
120
- const badge = text?.badge?.(state) ?? formatBadge(state);
152
+ const badge = text?.badge?.(state);
121
153
  const intent = hudIntentDisplay(text?.intent?.(state) ?? state.intent, 'native');
122
154
  const error = text?.error?.(state) ?? state.error;
123
155
  const badgeTone = badgeToneForStatus(state.status);
156
+ const finished =
157
+ FINISHED_ROW_FITS && state.finished
158
+ ? hudFinishedLine({ nodeId: 'finished', ...state.finished })
159
+ : null;
160
+ const progress = FINISHED_ROW_FITS ? formatProgress(state.currentStep, state.totalSteps) : '';
124
161
  const overlay = (
125
162
  <View
126
163
  onLayout={onLayout}
@@ -141,39 +178,67 @@ export function RecipeHud({
141
178
  { overflow: 'hidden', flexDirection: 'row' },
142
179
  ]}
143
180
  >
144
- <Text
145
- numberOfLines={1}
146
- allowFontScaling={false}
147
- style={[
148
- styles.badgeText,
149
- badgeTone === 'fail'
150
- ? styles.badgeTextFail
151
- : badgeTone === 'pass'
152
- ? styles.badgeTextPass
153
- : styles.badgeTextRunning,
154
- customStyles?.badgeText,
155
- badgeTone === 'fail'
156
- ? customStyles?.badgeTextFail
157
- : badgeTone === 'pass'
158
- ? customStyles?.badgeTextPass
159
- : customStyles?.badgeTextRunning,
160
- { maxWidth: 80, marginRight: 8 },
161
- ]}
162
- >
163
- {badge}
164
- </Text>
181
+ {badge ? (
182
+ <Text
183
+ numberOfLines={1}
184
+ allowFontScaling={false}
185
+ style={[
186
+ styles.badgeText,
187
+ badgeTone === 'fail'
188
+ ? styles.badgeTextFail
189
+ : badgeTone === 'pass'
190
+ ? styles.badgeTextPass
191
+ : styles.badgeTextRunning,
192
+ customStyles?.badgeText,
193
+ badgeTone === 'fail'
194
+ ? customStyles?.badgeTextFail
195
+ : badgeTone === 'pass'
196
+ ? customStyles?.badgeTextPass
197
+ : customStyles?.badgeTextRunning,
198
+ { maxWidth: 80, marginRight: 8 },
199
+ ]}
200
+ >
201
+ {badge}
202
+ </Text>
203
+ ) : null}
165
204
  <View style={{ flex: 1, minWidth: 0 }}>
166
205
  <Text
167
206
  {...intent.nativeProps}
168
- style={[styles.line, customStyles?.line, customStyles?.intent, intent.nativeStyle]}
207
+ style={[
208
+ styles.line,
209
+ state.status === 'fail' ? styles.failed : undefined,
210
+ customStyles?.line,
211
+ customStyles?.intent,
212
+ intent.nativeStyle,
213
+ ]}
169
214
  >
170
215
  {intent.text}
171
216
  </Text>
217
+ {finished || progress ? (
218
+ <View style={styles.finishedRow}>
219
+ <Text
220
+ numberOfLines={1}
221
+ allowFontScaling={false}
222
+ style={[styles.finished, finished?.tone === 'fail' ? styles.failed : undefined]}
223
+ >
224
+ {finished?.text}
225
+ </Text>
226
+ {progress ? (
227
+ <Text numberOfLines={1} allowFontScaling={false} style={styles.progress}>
228
+ {progress}
229
+ </Text>
230
+ ) : null}
231
+ </View>
232
+ ) : null}
172
233
  {error ? (
173
234
  <Text
174
235
  numberOfLines={1}
175
236
  allowFontScaling={false}
176
- style={[styles.error, customStyles?.error, { lineHeight: 16, maxHeight: 16 }]}
237
+ style={[
238
+ styles.error,
239
+ customStyles?.error,
240
+ { lineHeight: ERROR_LINE_HEIGHT, maxHeight: ERROR_LINE_HEIGHT },
241
+ ]}
177
242
  >
178
243
  {error}
179
244
  </Text>
@@ -188,12 +253,6 @@ export function RecipeHud({
188
253
  );
189
254
  }
190
255
 
191
- function formatBadge(state: FarmslotRecipeHudState): string {
192
- const status = state.status === 'running' ? 'run' : state.status;
193
- const progress = formatProgress(state.currentStep, state.totalSteps);
194
- return [status, progress].filter(Boolean).join(' ').toUpperCase();
195
- }
196
-
197
256
  function formatProgress(currentStep?: number, totalSteps?: number): string {
198
257
  if (!currentStep || !totalSteps) return '';
199
258
  return `${currentStep}/${totalSteps}`;
@@ -214,7 +273,7 @@ const styles = StyleSheet.create({
214
273
  zIndex: 9999,
215
274
  backgroundColor: 'rgba(0, 0, 0, 0.58)',
216
275
  paddingHorizontal: 10,
217
- paddingVertical: 3,
276
+ paddingVertical: PADDING_VERTICAL,
218
277
  },
219
278
  line: {
220
279
  color: '#ffffff',
@@ -235,6 +294,24 @@ const styles = StyleSheet.create({
235
294
  badgeTextFail: {
236
295
  color: '#ff4d4f',
237
296
  },
297
+ finishedRow: {
298
+ flexDirection: 'row',
299
+ },
300
+ finished: {
301
+ flex: 1,
302
+ color: HUD_COLORS.muted,
303
+ fontSize: HUD_NATIVE_FINISHED_FONT_SIZE,
304
+ lineHeight: HUD_NATIVE_FINISHED_LINE_HEIGHT,
305
+ },
306
+ failed: {
307
+ color: HUD_COLORS.fail,
308
+ },
309
+ progress: {
310
+ marginLeft: 6,
311
+ color: HUD_COLORS.muted,
312
+ fontSize: HUD_NATIVE_FINISHED_FONT_SIZE,
313
+ lineHeight: HUD_NATIVE_FINISHED_LINE_HEIGHT,
314
+ },
238
315
  error: {
239
316
  color: '#e6e6e6',
240
317
  fontSize: 10,