diff --git a/src/core/i18n/translations/de.json b/src/core/i18n/translations/de.json index ea7ec337..6e26fad3 100644 --- a/src/core/i18n/translations/de.json +++ b/src/core/i18n/translations/de.json @@ -2790,7 +2790,26 @@ "run_summary_duration": "Gesamtdauer", "run_summary_step_link": "Schritt {index}: {status}", "bytes_label": "Bytes", - "constant_input_persistence_hint": "Konstante Eingaben bleiben in dieser Browser-Sitzung am Schritt erhalten und gehen beim Bearbeiten oder Sortieren nicht verloren. Speichern, JSON-Export und Share-URL entfernen sie vor dem Schreiben oder Kopieren." + "constant_input_persistence_hint": "Konstante Eingaben bleiben in dieser Browser-Sitzung am Schritt erhalten und gehen beim Bearbeiten oder Sortieren nicht verloren. Speichern, JSON-Export und Share-URL entfernen sie vor dem Schreiben oder Kopieren.", + "validation_checks_title": "Was die Validierung prueft", + "validation_check_schema": "Recipe-Schemaversion, erforderliche IDs, Einstellungen und Step-Daten als einfache Objekte.", + "validation_check_adapters": "Jeder Step muss einen installierten deterministischen Pipeline-Adapter und die aktuelle Adapterversion verwenden.", + "validation_check_options": "Oeffentliche Step-Optionen muessen zu den unterstuetzten Werten und Bereichen des jeweiligen Adapters passen.", + "validation_check_edges": "Explizite Edges muessen, falls vorhanden, eine verbundene lineare Kette ohne Zyklen bilden.", + "validation_step_limit": "Aktuelle Schrittzahl: {count}/{max}. Das Limit haelt browserlokale Laeufe reaktionsschnell und verhindert zu grosse Zwischen-Payloads.", + "validation_repeat_tools": "Tools duerfen in einer Recipe wiederholt werden, wenn jeder Step eine eindeutige ID und gueltige Optionen hat.", + "validation_linear_only": "Der Executor ist nur linear: Jeder Step erhaelt die vorherige Ausgabe, ausser der Step nutzt konstante Eingabe.", + "validation_unsupported_combinations": "Input/Output-Typkonflikte erscheinen als Warnungen neben betroffenen Steps; nutzen Sie konstante Eingabe oder sortieren Sie Steps um, wenn eine Uebergabe nicht sinnvoll ist.", + "usage_guide_link": "Nutzungsanleitung", + "usage_guide_title": "Pipeline-Builder-Nutzungsanleitung", + "usage_guide_linear_title": "Lineare Ausfuehrung", + "usage_guide_linear_body": "Recipes laufen von oben nach unten. Die vorherige Ausgabe wird an den naechsten Step uebergeben, ausser ein Step ist auf konstante Eingabe gesetzt.", + "usage_guide_limits_title": "Step- und Payload-Limits", + "usage_guide_limits_body": "Recipes koennen bis zu {max} Steps enthalten. Der Browser erzwingt ausserdem Eingabe- und Ausgabe-Bytelimits, damit lokale Laeufe reaktionsschnell bleiben.", + "usage_guide_composition_title": "Kompositionsregeln", + "usage_guide_composition_body": "Tools koennen wiederholt werden. Die Validierung prueft Adapterversionen und Optionen, waehrend Kompatibilitaetshinweise Uebergaben wie Textausgabe in JSON-Eingabe markieren.", + "usage_guide_privacy_title": "Datenschutzgrenze", + "usage_guide_privacy_body": "Save, Export JSON und Share URL behalten nur Struktur und oeffentliche Optionen. Laufzeiteingaben, Ausgaben, Logs, Dateien und konstante Eingaben werden entfernt." }, "saml_decoder": { "title": "SAML-Dekoder", diff --git a/src/core/i18n/translations/en.json b/src/core/i18n/translations/en.json index 9df2bee7..e5fce829 100644 --- a/src/core/i18n/translations/en.json +++ b/src/core/i18n/translations/en.json @@ -2741,6 +2741,25 @@ "import_recipe": "Import JSON", "validation": "Validation", "validation_ok": "Recipe is valid for the linear MVP executor.", + "validation_checks_title": "What validation checks", + "validation_check_schema": "Recipe schema version, required IDs, settings, and plain-object step data.", + "validation_check_adapters": "Every step must use an installed deterministic Pipeline adapter and the current adapter version.", + "validation_check_options": "Public step options must match each adapter's supported values and ranges.", + "validation_check_edges": "Explicit edges, when present, must form one connected linear chain without cycles.", + "validation_step_limit": "Current step count: {count}/{max}. The limit keeps browser-only runs responsive and prevents oversized intermediate payloads.", + "validation_repeat_tools": "Tools may repeat in one recipe when each step has a unique ID and valid options.", + "validation_linear_only": "The executor is linear-only: each step receives the previous output unless that step uses Constant input.", + "validation_unsupported_combinations": "Input/output type mismatches are shown as warnings beside affected steps; use Constant input or reorder steps when a handoff is not meaningful.", + "usage_guide_link": "Usage guide", + "usage_guide_title": "Pipeline Builder usage guide", + "usage_guide_linear_title": "Linear execution", + "usage_guide_linear_body": "Recipes run from top to bottom. Previous output is passed into the next step unless a step is set to Constant input.", + "usage_guide_limits_title": "Step and payload limits", + "usage_guide_limits_body": "Recipes can include up to {max} steps. The browser also enforces input and output byte limits so local runs stay responsive.", + "usage_guide_composition_title": "Composition rules", + "usage_guide_composition_body": "Tools can repeat. Validation checks adapter versions and options, while compatibility hints flag handoffs such as text output into JSON input.", + "usage_guide_privacy_title": "Privacy boundary", + "usage_guide_privacy_body": "Save, Export JSON, and Share URL keep structure and public options only. Runtime input, outputs, logs, files, and Constant input are stripped.", "no_options": "This adapter has no configurable public options.", "option_mode": "Mode", "option_indent": "Indent", diff --git a/src/core/i18n/translations/fr.json b/src/core/i18n/translations/fr.json index 4bf256cf..5aa5be72 100644 --- a/src/core/i18n/translations/fr.json +++ b/src/core/i18n/translations/fr.json @@ -2790,7 +2790,26 @@ "run_summary_duration": "Durée totale", "run_summary_step_link": "Étape {index} : {status}", "bytes_label": "octets", - "constant_input_persistence_hint": "L’entrée constante reste attachée à cette étape dans la session actuelle du navigateur et n’est pas perdue lors de l’édition ou du réordonnancement. Save, Export JSON et Share URL la retirent avant d’écrire ou copier la recipe." + "constant_input_persistence_hint": "L’entrée constante reste attachée à cette étape dans la session actuelle du navigateur et n’est pas perdue lors de l’édition ou du réordonnancement. Save, Export JSON et Share URL la retirent avant d’écrire ou copier la recipe.", + "validation_checks_title": "Ce que la validation verifie", + "validation_check_schema": "Version du schema de recipe, IDs requis, parametres et donnees de step sous forme d'objet simple.", + "validation_check_adapters": "Chaque step doit utiliser un adaptateur Pipeline deterministe installe et sa version actuelle.", + "validation_check_options": "Les options publiques de step doivent correspondre aux valeurs et plages prises en charge par chaque adaptateur.", + "validation_check_edges": "Les edges explicites, s'ils existent, doivent former une seule chaine lineaire connectee sans cycle.", + "validation_step_limit": "Nombre de steps actuel : {count}/{max}. Cette limite garde les executions navigateur reactives et evite les payloads intermediaires trop grands.", + "validation_repeat_tools": "Les outils peuvent se repeter dans une recipe si chaque step a un ID unique et des options valides.", + "validation_linear_only": "L'executor est uniquement lineaire : chaque step recoit la sortie precedente sauf si elle utilise une entree constante.", + "validation_unsupported_combinations": "Les incompatibilites de type entree/sortie apparaissent comme avertissements pres des steps concernees ; utilisez une entree constante ou reordonnez les steps si le passage n'a pas de sens.", + "usage_guide_link": "Guide d'utilisation", + "usage_guide_title": "Guide d'utilisation du constructeur de pipeline", + "usage_guide_linear_title": "Execution lineaire", + "usage_guide_linear_body": "Les recipes s'executent de haut en bas. La sortie precedente est transmise a la step suivante sauf si une step utilise une entree constante.", + "usage_guide_limits_title": "Limites de steps et de payload", + "usage_guide_limits_body": "Les recipes peuvent inclure jusqu'a {max} steps. Le navigateur applique aussi des limites d'octets en entree et sortie pour garder les executions locales reactives.", + "usage_guide_composition_title": "Regles de composition", + "usage_guide_composition_body": "Les outils peuvent se repeter. La validation verifie versions d'adaptateur et options, tandis que les indications de compatibilite signalent les passages comme une sortie texte vers une entree JSON.", + "usage_guide_privacy_title": "Limite de confidentialite", + "usage_guide_privacy_body": "Save, Export JSON et Share URL conservent uniquement la structure et les options publiques. Entrees d'execution, sorties, logs, fichiers et entrees constantes sont retires." }, "saml_decoder": { "title": "Décodeur SAML", diff --git a/src/core/i18n/translations/ja.json b/src/core/i18n/translations/ja.json index 02ba3854..6d9ba550 100644 --- a/src/core/i18n/translations/ja.json +++ b/src/core/i18n/translations/ja.json @@ -2790,7 +2790,26 @@ "run_summary_duration": "合計時間", "run_summary_step_link": "ステップ {index}: {status}", "bytes_label": "バイト", - "constant_input_persistence_hint": "固定入力は現在のブラウザーセッションでこのステップに保持され、編集や並べ替えでは失われません。保存、JSON 書き出し、共有 URL の前に削除されます。" + "constant_input_persistence_hint": "固定入力は現在のブラウザーセッションでこのステップに保持され、編集や並べ替えでは失われません。保存、JSON 書き出し、共有 URL の前に削除されます。", + "validation_checks_title": "検証する内容", + "validation_check_schema": "Recipe schema バージョン、必須 ID、設定、プレーンオブジェクトのステップデータ。", + "validation_check_adapters": "各ステップは、インストール済みの決定的 Pipeline アダプタと現行アダプタバージョンを使う必要があります。", + "validation_check_options": "公開ステップオプションは、各アダプタがサポートする値と範囲に合う必要があります。", + "validation_check_edges": "明示 edges がある場合、循環のない単一の連結線形チェーンである必要があります。", + "validation_step_limit": "現在のステップ数: {count}/{max}。この制限はブラウザーのみの実行を応答性高く保ち、中間 payload の過大化を防ぐためです。", + "validation_repeat_tools": "各ステップに一意の ID と有効なオプションがあれば、1 つの recipe でツールを繰り返せます。", + "validation_linear_only": "実行器は線形のみです: 各ステップは、固定入力でない限り前の出力を受け取ります。", + "validation_unsupported_combinations": "入力/出力型の不一致は対象ステップの横に警告表示されます。受け渡しが意味を持たない場合は固定入力または並べ替えを使ってください。", + "usage_guide_link": "使い方ガイド", + "usage_guide_title": "パイプラインビルダー使い方ガイド", + "usage_guide_linear_title": "線形実行", + "usage_guide_linear_body": "Recipe は上から下へ実行されます。固定入力のステップを除き、前の出力が次のステップに渡されます。", + "usage_guide_limits_title": "ステップと payload 制限", + "usage_guide_limits_body": "Recipe は最大 {max} ステップまで含められます。ブラウザーは入力と出力のバイト制限も適用し、ローカル実行の応答性を保ちます。", + "usage_guide_composition_title": "組み合わせルール", + "usage_guide_composition_body": "ツールは繰り返せます。検証はアダプタバージョンとオプションを確認し、互換性ヒントはテキスト出力から JSON 入力のような受け渡しを示します。", + "usage_guide_privacy_title": "プライバシー境界", + "usage_guide_privacy_body": "保存、JSON 書き出し、共有 URL は構造と公開オプションのみ保持します。実行時入力、出力、ログ、ファイル、固定入力は削除されます。" }, "saml_decoder": { "title": "SAML デコーダー", diff --git a/src/core/i18n/translations/ko.json b/src/core/i18n/translations/ko.json index 75138140..36c9a5b8 100644 --- a/src/core/i18n/translations/ko.json +++ b/src/core/i18n/translations/ko.json @@ -2790,7 +2790,26 @@ "run_summary_duration": "총 시간", "run_summary_step_link": "단계 {index}: {status}", "bytes_label": "바이트", - "constant_input_persistence_hint": "고정 입력은 현재 브라우저 세션에서 이 단계와 함께 유지되며 편집하거나 순서를 바꿔도 사라지지 않습니다. 저장, JSON 내보내기, 공유 URL 전에는 제거됩니다." + "constant_input_persistence_hint": "고정 입력은 현재 브라우저 세션에서 이 단계와 함께 유지되며 편집하거나 순서를 바꿔도 사라지지 않습니다. 저장, JSON 내보내기, 공유 URL 전에는 제거됩니다.", + "validation_checks_title": "검증 항목", + "validation_check_schema": "Recipe schema 버전, 필수 ID, 설정, plain object 단계 데이터.", + "validation_check_adapters": "각 단계는 설치된 결정적 Pipeline 어댑터와 현재 어댑터 버전을 사용해야 합니다.", + "validation_check_options": "공개 단계 옵션은 각 어댑터가 지원하는 값과 범위와 맞아야 합니다.", + "validation_check_edges": "명시적 edges가 있으면 순환 없는 하나의 연결된 선형 체인을 이뤄야 합니다.", + "validation_step_limit": "현재 단계 수: {count}/{max}. 이 제한은 브라우저 내 실행을 빠르게 유지하고 중간 payload 과대화를 막기 위한 것입니다.", + "validation_repeat_tools": "각 단계에 고유 ID와 유효한 옵션이 있으면 하나의 recipe에서 도구를 반복할 수 있습니다.", + "validation_linear_only": "실행기는 선형만 지원합니다. 고정 입력을 사용하는 단계가 아니면 각 단계는 이전 출력을 받습니다.", + "validation_unsupported_combinations": "입력/출력 유형 불일치는 해당 단계 옆에 경고로 표시됩니다. 인계가 의미 없을 때는 고정 입력이나 단계 순서 변경을 사용하세요.", + "usage_guide_link": "사용 가이드", + "usage_guide_title": "파이프라인 빌더 사용 가이드", + "usage_guide_linear_title": "선형 실행", + "usage_guide_linear_body": "Recipe는 위에서 아래로 실행됩니다. 단계가 고정 입력으로 설정되지 않으면 이전 출력이 다음 단계로 전달됩니다.", + "usage_guide_limits_title": "단계 및 payload 제한", + "usage_guide_limits_body": "Recipe는 최대 {max}개 단계까지 포함할 수 있습니다. 브라우저는 로컬 실행의 응답성을 유지하기 위해 입력과 출력 바이트 제한도 적용합니다.", + "usage_guide_composition_title": "구성 규칙", + "usage_guide_composition_body": "도구는 반복할 수 있습니다. 검증은 어댑터 버전과 옵션을 확인하며, 호환성 힌트는 text 출력을 JSON 입력으로 넘기는 경우처럼 인계를 표시합니다.", + "usage_guide_privacy_title": "개인정보 경계", + "usage_guide_privacy_body": "저장, JSON 내보내기, 공유 URL은 구조와 공개 옵션만 유지합니다. 런타임 입력, 출력, 로그, 파일, 고정 입력은 제거됩니다." }, "saml_decoder": { "title": "SAML 디코더", diff --git a/src/core/i18n/translations/zh-CN.json b/src/core/i18n/translations/zh-CN.json index 3ca3e864..eac08f87 100644 --- a/src/core/i18n/translations/zh-CN.json +++ b/src/core/i18n/translations/zh-CN.json @@ -2790,7 +2790,26 @@ "run_summary_duration": "总耗时", "run_summary_step_link": "步骤 {index}:{status}", "bytes_label": "字节", - "constant_input_persistence_hint": "固定输入会在当前浏览器会话中随此步骤保留,编辑或重排时不会丢失。保存、导出 JSON 和分享 URL 前会先剥离它。" + "constant_input_persistence_hint": "固定输入会在当前浏览器会话中随此步骤保留,编辑或重排时不会丢失。保存、导出 JSON 和分享 URL 前会先剥离它。", + "validation_checks_title": "验证会检查什么", + "validation_check_schema": "Recipe schema 版本、必填 ID、设置和普通对象步骤数据。", + "validation_check_adapters": "每个步骤都必须使用已安装的确定性 Pipeline 适配器和当前适配器版本。", + "validation_check_options": "公开步骤选项必须符合对应适配器支持的值和范围。", + "validation_check_edges": "如果提供显式 edges,它们必须组成一条无环的连通线性链。", + "validation_step_limit": "当前步骤数:{count}/{max}。该限制用于保持纯浏览器运行响应迅速,并防止中间 payload 过大。", + "validation_repeat_tools": "只要每个步骤有唯一 ID 和有效选项,同一 recipe 里可以重复使用工具。", + "validation_linear_only": "执行器仅支持线性流程:每个步骤接收上一步输出,除非该步骤使用固定输入。", + "validation_unsupported_combinations": "输入/输出类型不匹配会在受影响步骤旁显示警告;交接不合理时请使用固定输入或重排步骤。", + "usage_guide_link": "使用指南", + "usage_guide_title": "管道构建器使用指南", + "usage_guide_linear_title": "线性执行", + "usage_guide_linear_body": "Recipe 从上到下运行。上一步输出会传给下一步,除非某步设为固定输入。", + "usage_guide_limits_title": "步骤和 payload 限制", + "usage_guide_limits_body": "Recipe 最多可包含 {max} 个步骤。浏览器还会强制输入和输出字节限制,保持本地运行响应迅速。", + "usage_guide_composition_title": "组合规则", + "usage_guide_composition_body": "工具可以重复。验证会检查适配器版本和选项,兼容性提示会标出类似文本输出接入 JSON 输入的交接。", + "usage_guide_privacy_title": "隐私边界", + "usage_guide_privacy_body": "保存、导出 JSON 和分享 URL 仅保留结构和公开选项。运行时输入、输出、日志、文件和固定输入都会被剥离。" }, "saml_decoder": { "title": "SAML 解码器", diff --git a/src/core/i18n/translations/zh-TW.json b/src/core/i18n/translations/zh-TW.json index 1de64e49..b751ab9b 100644 --- a/src/core/i18n/translations/zh-TW.json +++ b/src/core/i18n/translations/zh-TW.json @@ -2790,7 +2790,26 @@ "run_summary_duration": "總耗時", "run_summary_step_link": "步驟 {index}:{status}", "bytes_label": "位元組", - "constant_input_persistence_hint": "固定輸入會在目前瀏覽器工作階段中隨此步驟保留,編輯或重排時不會遺失。儲存、匯出 JSON 和分享 URL 前會先移除它。" + "constant_input_persistence_hint": "固定輸入會在目前瀏覽器工作階段中隨此步驟保留,編輯或重排時不會遺失。儲存、匯出 JSON 和分享 URL 前會先移除它。", + "validation_checks_title": "驗證會檢查什麼", + "validation_check_schema": "Recipe schema 版本、必填 ID、設定和普通物件步驟資料。", + "validation_check_adapters": "每個步驟都必須使用已安裝的確定性 Pipeline 適配器和目前適配器版本。", + "validation_check_options": "公開步驟選項必須符合對應適配器支援的值和範圍。", + "validation_check_edges": "如果提供顯式 edges,它們必須組成一條無環的連通線性鏈。", + "validation_step_limit": "目前步驟數:{count}/{max}。此限制用來保持純瀏覽器執行回應迅速,並防止中間 payload 過大。", + "validation_repeat_tools": "只要每個步驟有唯一 ID 和有效選項,同一 recipe 裡可以重複使用工具。", + "validation_linear_only": "執行器僅支援線性流程:每個步驟接收上一步輸出,除非該步驟使用固定輸入。", + "validation_unsupported_combinations": "輸入/輸出類型不匹配會在受影響步驟旁顯示警告;交接不合理時請使用固定輸入或重排步驟。", + "usage_guide_link": "使用指南", + "usage_guide_title": "管道建構器使用指南", + "usage_guide_linear_title": "線性執行", + "usage_guide_linear_body": "Recipe 從上到下執行。上一步輸出會傳給下一步,除非某步設為固定輸入。", + "usage_guide_limits_title": "步驟和 payload 限制", + "usage_guide_limits_body": "Recipe 最多可包含 {max} 個步驟。瀏覽器還會強制輸入和輸出位元組限制,保持本地執行回應迅速。", + "usage_guide_composition_title": "組合規則", + "usage_guide_composition_body": "工具可以重複。驗證會檢查適配器版本和選項,相容性提示會標出類似文本輸出接入 JSON 輸入的交接。", + "usage_guide_privacy_title": "隱私邊界", + "usage_guide_privacy_body": "儲存、匯出 JSON 和分享 URL 僅保留結構和公開選項。執行時輸入、輸出、日誌、檔案和固定輸入都會被移除。" }, "saml_decoder": { "title": "SAML 解碼器", diff --git a/src/features/tools/pipeline-builder/page.tsx b/src/features/tools/pipeline-builder/page.tsx index f047e2b9..071833e6 100644 --- a/src/features/tools/pipeline-builder/page.tsx +++ b/src/features/tools/pipeline-builder/page.tsx @@ -1,16 +1,7 @@ "use client" import * as React from "react" -import { - Copy, - Download, - FileInput, - Link2, - ListChecks, - Play, - Save, - Workflow, -} from "lucide-react" +import { Copy, Download, FileInput, Link2, ListChecks, Play, Save, Workflow } from "lucide-react" import { toast } from "sonner" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" @@ -48,6 +39,7 @@ import { PipelineStepDiagnostics } from "./pipeline-step-diagnostics" import { PipelineStepInspector } from "./pipeline-step-inspector" import { PipelineStepList } from "./pipeline-step-list" import { PipelineTemplateList } from "./pipeline-template-list" +import { PipelineUsageGuide } from "./pipeline-usage-guide" import type { OptionValue } from "./types" const ONBOARDING_DISMISSED_KEY = "byteflow:pipeline-builder:onboarding-dismissed" @@ -538,6 +530,7 @@ export function PipelineBuilderPage() { result={result} text={text} /> +
-

{text("validation")}

+
+

{text("validation")}

+ + {text("usage_guide_link")} + +
{validation.ok ? (

{text("validation_ok")}

) : ( @@ -164,6 +172,23 @@ export function PipelineStepInspector({ {validation.errors.map((error) =>
  • {error}
  • )} )} +
    +
    +

    {text("validation_checks_title")}

    +
      +
    • {text("validation_check_schema")}
    • +
    • {text("validation_check_adapters")}
    • +
    • {text("validation_check_options")}
    • +
    • {text("validation_check_edges")}
    • +
    +
    +
    +

    {text("validation_step_limit").replace("{count}", String(recipe.steps.length)).replace("{max}", String(recipe.settings.maxSteps))}

    +

    {text("validation_repeat_tools")}

    +

    {text("validation_linear_only")}

    +

    {text("validation_unsupported_combinations")}

    +
    +
    ) diff --git a/src/features/tools/pipeline-builder/pipeline-usage-guide.tsx b/src/features/tools/pipeline-builder/pipeline-usage-guide.tsx new file mode 100644 index 00000000..28e8a9c5 --- /dev/null +++ b/src/features/tools/pipeline-builder/pipeline-usage-guide.tsx @@ -0,0 +1,30 @@ +type PipelineUsageGuideProps = { + maxSteps: number + text: (key: string) => string +} + +export function PipelineUsageGuide({ maxSteps, text }: PipelineUsageGuideProps) { + return ( +
    +

    {text("usage_guide_title")}

    +
    +
    +

    {text("usage_guide_linear_title")}

    +

    {text("usage_guide_linear_body")}

    +
    +
    +

    {text("usage_guide_limits_title")}

    +

    {text("usage_guide_limits_body").replace("{max}", String(maxSteps))}

    +
    +
    +

    {text("usage_guide_composition_title")}

    +

    {text("usage_guide_composition_body")}

    +
    +
    +

    {text("usage_guide_privacy_title")}

    +

    {text("usage_guide_privacy_body")}

    +
    +
    +
    + ) +} diff --git a/tests/component/phase3-pipeline-builder-page.test.tsx b/tests/component/phase3-pipeline-builder-page.test.tsx index c161e914..5cf49288 100644 --- a/tests/component/phase3-pipeline-builder-page.test.tsx +++ b/tests/component/phase3-pipeline-builder-page.test.tsx @@ -116,6 +116,8 @@ describe("phase 3 pipeline builder page", () => { expect(screen.getByText("JSON TypeScript contract review")).toBeInTheDocument() expect(screen.getByText("Image social export manifest")).toBeInTheDocument() expect(screen.getByText("Step diagnostics")).toBeInTheDocument() + expect(screen.getByRole("heading", { name: "Pipeline Builder usage guide" })).toBeInTheDocument() + expect(screen.getByText("Recipes run from top to bottom. Previous output is passed into the next step unless a step is set to Constant input.")).toBeInTheDocument() expect(screen.getByRole("button", { name: /Run Recipe/i })).toBeInTheDocument() expect(screen.getAllByRole("button", { name: /Export JSON/i }).length).toBeGreaterThan(0) expect(screen.getByRole("button", { name: /Share URL/i })).toBeInTheDocument() @@ -180,6 +182,12 @@ describe("phase 3 pipeline builder page", () => { expect(screen.getByRole("heading", { name: "Recipe settings" })).toBeInTheDocument() expect(screen.getByRole("switch", { name: "Stop on error" })).toBeChecked() expect(screen.getByText("Check handoff: text output into json input.")).toBeInTheDocument() + expect(screen.getByRole("link", { name: "Usage guide" })).toHaveAttribute("href", "#pipeline-usage-guide") + expect(screen.getByText("What validation checks")).toBeInTheDocument() + expect(screen.getByText("Current step count: 2/12. The limit keeps browser-only runs responsive and prevents oversized intermediate payloads.")).toBeInTheDocument() + expect(screen.getByText("Tools may repeat in one recipe when each step has a unique ID and valid options.")).toBeInTheDocument() + expect(screen.getByText("The executor is linear-only: each step receives the previous output unless that step uses Constant input.")).toBeInTheDocument() + expect(screen.getByText("Input/output type mismatches are shown as warnings beside affected steps; use Constant input or reorder steps when a handoff is not meaningful.")).toBeInTheDocument() expect(screen.getByText(/Constant step input is used only for the current run/i)).toBeInTheDocument() expect(screen.getByText("text input -> text output")).toBeInTheDocument() }) diff --git a/tests/unit/pipeline-foundation.test.ts b/tests/unit/pipeline-foundation.test.ts index 6f8198e4..8aa58bd8 100644 --- a/tests/unit/pipeline-foundation.test.ts +++ b/tests/unit/pipeline-foundation.test.ts @@ -504,6 +504,26 @@ describe("pipeline foundation", () => { expect(validateRecipe(recipe)).toEqual({ ok: true, errors: [] }) }) + it("rejects recipes above the configured step limit", () => { + const result = validateRecipe(buildRecipe({ + steps: Array.from({ length: 3 }, (_value, index) => ({ + id: `step_${index + 1}`, + toolKey: "multiple_whitespace_remover", + adapterVersion: 1, + inputMode: "previous_output", + options: {}, + })), + edges: [], + settings: { + ...DEFAULT_RECIPE_SETTINGS, + maxSteps: 2, + }, + })) + + expect(result.ok).toBe(false) + expect(result.errors).toContain("Recipe has more than 2 steps.") + }) + it.each([ ["self loop", [{ fromStepId: "format", toStepId: "format" }], "Edge format -> format is a self-loop."], ["branch", [{ fromStepId: "format", toStepId: "encode" }, { fromStepId: "format", toStepId: "scrub" }], "Step format has multiple outgoing edges."],