diff --git a/api/index.php b/api/index.php
index fec784a..dddc0ee 100644
--- a/api/index.php
+++ b/api/index.php
@@ -5571,7 +5571,14 @@ PROMPT;
return;
}
- // ── Post-process: fuzzy-match ingredients → inventory (same as generateRecipe) ──
+ // Normalize steps: Gemini sometimes returns [{"text":"..."}, ...] instead of ["...", ...]
+ if (!empty($recipe['steps']) && is_array($recipe['steps'])) {
+ $recipe['steps'] = array_values(array_map(function($s) {
+ if (is_string($s)) return $s;
+ if (is_array($s)) return $s['text'] ?? $s['description'] ?? $s['step'] ?? json_encode($s, JSON_UNESCAPED_UNICODE);
+ return (string)$s;
+ }, $recipe['steps']));
+ }
if (!empty($recipe['ingredients'])) {
$itemsLookup = [];
foreach ($items as $item) {
diff --git a/assets/js/app.js b/assets/js/app.js
index 58de99b..8fbc44e 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -13161,8 +13161,7 @@ function renderRecipe(r) {
// Steps
html += `
${t('recipes.steps_title')}
`;
(r.steps || []).forEach(step => {
- const cleanStep = step.replace(/^Passo\s*\d+\s*:\s*/i, '');
- html += `- ${cleanStep}
`;
+ html += `- ${_stepStr(step)}
`;
});
html += '
';
@@ -13179,6 +13178,11 @@ let _cookingRecipe = null;
let _cookingStep = 0;
let _cookingTTS = true;
let _cookingVisited = new Set(); // indices of steps already seen
+
+// Safely extract step text regardless of whether it's a string or an object
+// (Gemini sometimes returns [{text:"..."}, ...] instead of ["...", ...])
+const _stepStr = s => String((s !== null && typeof s === 'object') ? (s.text ?? s.description ?? s.step ?? '') : (s ?? '')).replace(/^Passo\s*\d+\s*[:.]\s*/i, '');
+
let _cookingWheelBound = false;
let _cookingWheelTouchStartY = null;
let _cookingWheelLastNavTs = 0;
@@ -13281,7 +13285,7 @@ function startCookingMode() {
try { screen.orientation?.lock('portrait').catch(() => {}); } catch (_) { /* ignore */ }
renderCookingStep();
if (_cookingTTS) {
- const text = ((_cookingRecipe.steps || [])[_cookingStep] || '').replace(/^Passo\s*\d+\s*[:.]\s*/i, '');
+ const text = _stepStr((_cookingRecipe.steps || [])[_cookingStep]);
speakCookingStep(text);
}
}
@@ -13432,7 +13436,7 @@ function renderCookingStep() {
if (!_cookingRecipe) return;
const steps = _cookingRecipe.steps || [];
const step = steps[_cookingStep] || '';
- const cleanStep = step.replace(/^Passo\s*\d+\s*[:.]\s*/i, '');
+ const cleanStep = _stepStr(step);
const total = steps.length;
// Mark current step as visited
@@ -13445,7 +13449,7 @@ function renderCookingStep() {
const nextEl = document.getElementById('cooking-step-next');
if (prevEl) {
if (_cookingStep > 0) {
- prevEl.textContent = (steps[_cookingStep - 1] || '').replace(/^Passo\s*\d+\s*[:.]\s*/i, '');
+ prevEl.textContent = _stepStr(steps[_cookingStep - 1]);
prevEl.classList.remove('is-empty');
} else {
prevEl.textContent = '';
@@ -13454,7 +13458,7 @@ function renderCookingStep() {
}
if (nextEl) {
if (_cookingStep < total - 1) {
- nextEl.textContent = (steps[_cookingStep + 1] || '').replace(/^Passo\s*\d+\s*[:.]\s*/i, '');
+ nextEl.textContent = _stepStr(steps[_cookingStep + 1]);
nextEl.classList.remove('is-empty');
} else {
nextEl.textContent = '';
@@ -13617,7 +13621,7 @@ async function speakCookingStep(text) {
function replayCookingTTS() {
if (!_cookingRecipe) return;
const steps = _cookingRecipe.steps || [];
- const text = (steps[_cookingStep] || '').replace(/^Passo\s*\d+\s*[:.]\s*/i, '');
+ const text = _stepStr(steps[_cookingStep]);
if (text) speakCookingStep(text);
}
@@ -14180,7 +14184,7 @@ function toggleCookingTTS() {
btn.textContent = _cookingTTS ? '🔊' : '🔇';
if (_cookingTTS) {
const steps = _cookingRecipe?.steps || [];
- const text = (steps[_cookingStep] || '').replace(/^Passo\s*\d+\s*[:.]\s*/i, '');
+ const text = _stepStr(steps[_cookingStep]);
speakCookingStep(text);
}
}
@@ -14205,7 +14209,7 @@ function navigateCookingStep(delta) {
renderCookingStep();
_cookingStepFeedback();
if (_cookingTTS) {
- const text = ((_cookingRecipe.steps || [])[_cookingStep] || '').replace(/^Passo\s*\d+\s*[:.]\s*/i, '');
+ const text = _stepStr((_cookingRecipe.steps || [])[_cookingStep]);
speakCookingStep(text);
}
}