This is an automated email from the ASF dual-hosted git repository.

zqr10159 pushed a commit to branch 2.0.0
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git

commit 720d20bdc4ecb47d5e11eadd9a3678735431d808
Author: Logic <[email protected]>
AuthorDate: Wed Aug 26 09:54:46 2026 +0800

    feat: preserve guided instrumentation workflow
---
 web-app/src/assets/i18n/en-us.json                 |  38 +++++
 web-app/src/assets/i18n/ja-jp.json                 |  38 +++++
 web-app/src/assets/i18n/pt-br.json                 |  38 +++++
 web-app/src/assets/i18n/zh-cn.json                 |  38 +++++
 web-app/src/assets/i18n/zh-tw.json                 |  38 +++++
 .../instrumentation-configure-step-props.ts        |  42 +++++
 .../components/instrumentation-configure-step.tsx  | 162 ++++++--------------
 .../instrumentation-configure.module.css           | 138 ++---------------
 ...instrumentation-destination-selector.module.css | 170 +++++++++++++++++++++
 .../instrumentation-destination-selector.tsx       | 146 +++++++++++++-----
 .../instrumentation-guide-configuration.module.css |  68 +++++++++
 .../instrumentation-guide-configuration.tsx        | 148 ++++++++++++++++++
 .../components/instrumentation-guide-workspace.tsx |  37 ++++-
 .../instrumentation-guided-configuration.test.tsx  | 160 +++++++++++++++++++
 .../instrumentation-onboarding.module.css          |  33 +++-
 .../components/instrumentation-progress.tsx        |  21 ++-
 .../components/instrumentation-shell.module.css    |  55 +++++--
 .../components/instrumentation-v2-flow.test.tsx    |  91 +++++++----
 .../instrumentation/instrumentation-i18n.test.ts   |   9 ++
 .../instrumentation/model/instrumentation-flow.ts  |   2 +-
 .../model/instrumentation-guided-flow.ts           |  24 +++
 .../pages/instrumentation-page.test.tsx            |   5 +-
 .../instrumentation/pages/instrumentation-page.tsx | 140 +++++------------
 .../pages/instrumentation-stage-content.tsx        | 130 ++++++++++++++++
 24 files changed, 1320 insertions(+), 451 deletions(-)

diff --git a/web-app/src/assets/i18n/en-us.json 
b/web-app/src/assets/i18n/en-us.json
index 17986ec1cb..6b8932318f 100644
--- a/web-app/src/assets/i18n/en-us.json
+++ b/web-app/src/assets/i18n/en-us.json
@@ -645,6 +645,27 @@
       "contextTitle": "Service and destination",
       "configureTitle": "Configure",
       "configureDescription": "Name the service, choose where telemetry is 
sent, then generate the exact instructions.",
+      "guided": {
+        "stage": {
+          "source": "Source",
+          "service": "Service details",
+          "destination": "Intake path",
+          "guide": "Setup guide"
+        },
+        "serviceTitle": "Which service owns this telemetry?",
+        "serviceDescription": "These values become telemetry resource 
attributes used for querying, filtering, and correlation.",
+        "destinationTitle": "Where should telemetry be sent first?",
+        "destinationDescription": "Choose one intake path. Connection details 
are shown only for the current selection.",
+        "guideTitle": "Generate setup instructions",
+        "guideDescription": "Confirm the destination and authorization, then 
generate steps for this service.",
+        "currentIntake": "Current intake",
+        "selectAvailableDestination": "Choose an available intake path to 
review its connection details.",
+        "selectedDestination": "Intake path",
+        "online": "Online",
+        "collectorId": "Collector ID",
+        "destinationInstance": "Intake instance",
+        "copyEndpoint": "Copy {{transport}} endpoint"
+      },
       "serviceContext": "Service context",
       "installTitle": "Install and configure",
       "recipeLabel": "Instrumentation recipe",
@@ -885,6 +906,15 @@
     "tokenMissing": "Not provided",
     "token": {
       "generateAccess": "Generate access token",
+      "create": "Create access token",
+      "useExisting": "Use an existing token",
+      "cancelExisting": "Cancel",
+      "setupTitle": "Create a token to continue",
+      "setupDescription": "It will be added to this setup automatically. 
Continue once it is ready.",
+      "existingTitle": "Use an existing token",
+      "existingDescription": "Paste a token only if one has already been 
issued for OTLP ingestion.",
+      "readyTitle": "Access token ready",
+      "readyDescription": "It will be inserted into the setup guide 
automatically.",
       "generateTitle": "Generate access token",
       "generate": "Generate",
       "name": "Token name",
@@ -895,6 +925,7 @@
       "notGenerated": "No access token generated",
       "generateError": "The access token could not be generated.",
       "acknowledgementRequired": "Save this one-time token before leaving or 
starting over. It exists only in this page's memory.",
+      "guideAcknowledgementRequired": "This one-time token is visible in the 
guide below. Save it before leaving or starting over.",
       "acknowledge": "I saved this token",
       "plaintextBearerWarning": "The Bearer token will be transmitted over an 
unencrypted connection."
     },
@@ -940,6 +971,8 @@
     },
     "action": {
       "exit": "Back to dashboard",
+      "previous": "Previous",
+      "next": "Next",
       "continue": "Continue",
       "render": "Render instructions",
       "reviewContext": "Review context",
@@ -1286,6 +1319,8 @@
     "selectedPauseConfirm_other": "Pause {{count}} selected monitors?",
     "delete": "Delete",
     "deleteConfirm": "Delete the selected monitor data?",
+    "selectAll": "Select all monitors",
+    "selectOne": "Select {{name}}",
     "selected": "{{count}} selected",
     "clearSelection": "Clear selection",
     "success": "Monitor operation completed.",
@@ -1362,6 +1397,9 @@
     "average": "Avg",
     "samples": "Samples",
     "refreshHistory": "Refresh history",
+    "investigation": {
+      "open": "Related signals"
+    },
     "collapseHistory": "Collapse history",
     "expandHistory": "Expand history",
     "layout": {
diff --git a/web-app/src/assets/i18n/ja-jp.json 
b/web-app/src/assets/i18n/ja-jp.json
index 462e1e2c3a..3943ee92c0 100644
--- a/web-app/src/assets/i18n/ja-jp.json
+++ b/web-app/src/assets/i18n/ja-jp.json
@@ -642,6 +642,27 @@
       "contextTitle": "サービスと送信先",
       "configureTitle": "設定",
       "configureDescription": "サービス名と送信先を設定し、正確な接続手順を生成します。",
+      "guided": {
+        "stage": {
+          "source": "接続元",
+          "service": "サービス情報",
+          "destination": "取り込み経路",
+          "guide": "接続ガイド"
+        },
+        "serviceTitle": "このテレメトリはどのサービスのものですか?",
+        "serviceDescription": "これらの値は、検索、絞り込み、関連付けに使用するリソース属性になります。",
+        "destinationTitle": "テレメトリを最初にどこへ送信しますか?",
+        "destinationDescription": "取り込み経路を選択してください。接続情報は現在の選択にのみ表示されます。",
+        "guideTitle": "接続手順を生成",
+        "guideDescription": "送信先と認証方式を確認し、このサービス向けの手順を生成します。",
+        "currentIntake": "現在の取り込み先",
+        "selectAvailableDestination": "利用可能な取り込み経路を選択すると、接続情報が表示されます。",
+        "selectedDestination": "取り込み経路",
+        "online": "オンライン",
+        "collectorId": "Collector ID",
+        "destinationInstance": "取り込みインスタンス",
+        "copyEndpoint": "{{transport}} エンドポイントをコピー"
+      },
       "serviceContext": "サービスコンテキスト",
       "installTitle": "インストールと設定",
       "recipeLabel": "計装レシピ",
@@ -882,6 +903,15 @@
     "tokenMissing": "未入力",
     "token": {
       "generateAccess": "アクセストークンを生成",
+      "create": "アクセストークンを作成",
+      "useExisting": "既存のトークンを使用",
+      "cancelExisting": "キャンセル",
+      "setupTitle": "トークンを作成して続行",
+      "setupDescription": "トークンはこのセットアップに自動的に追加されます。作成後、次へ進めます。",
+      "existingTitle": "既存のトークンを使用",
+      "existingDescription": "OTLP 取り込み用トークンがすでに発行されている場合のみ貼り付けてください。",
+      "readyTitle": "アクセストークンの準備ができました",
+      "readyDescription": "次の手順でセットアップガイドに自動的に挿入されます。",
       "generateTitle": "アクセストークンを生成",
       "generate": "生成",
       "name": "トークン名",
@@ -892,6 +922,7 @@
       "notGenerated": "アクセストークンは未生成です",
       "generateError": "アクセストークンを生成できませんでした。",
       "acknowledgementRequired": 
"終了または最初からやり直す前に、この一度だけ表示されるトークンを保存してください。このページのメモリにのみ保持されています。",
+      "guideAcknowledgementRequired": 
"一度だけ表示されるトークンが下の接続ガイドにあります。終了または最初からやり直す前に保存してください。",
       "acknowledge": "トークンを保存しました",
       "plaintextBearerWarning": "Bearer トークンは暗号化されていない接続で送信されます。"
     },
@@ -937,6 +968,8 @@
     },
     "action": {
       "exit": "ダッシュボードに戻る",
+      "previous": "前へ",
+      "next": "次へ",
       "continue": "続行",
       "render": "手順を生成",
       "reviewContext": "コンテキストを確認",
@@ -1283,6 +1316,8 @@
     "selectedPauseConfirm_other": "選択した {{count}} 件の監視を一時停止しますか?",
     "delete": "削除",
     "deleteConfirm": "選択した監視データを削除しますか?",
+    "selectAll": "すべての監視を選択",
+    "selectOne": "{{name}} を選択",
     "selected": "{{count}} 件を選択",
     "clearSelection": "選択を解除",
     "success": "監視操作が完了しました。",
@@ -1359,6 +1394,9 @@
     "average": "平均",
     "samples": "サンプル",
     "refreshHistory": "履歴を更新",
+    "investigation": {
+      "open": "関連シグナル"
+    },
     "collapseHistory": "履歴を折りたたむ",
     "expandHistory": "履歴を展開",
     "layout": {
diff --git a/web-app/src/assets/i18n/pt-br.json 
b/web-app/src/assets/i18n/pt-br.json
index aeb2040972..4378ed0061 100644
--- a/web-app/src/assets/i18n/pt-br.json
+++ b/web-app/src/assets/i18n/pt-br.json
@@ -648,6 +648,27 @@
       "contextTitle": "Serviço e destino",
       "configureTitle": "Configurar",
       "configureDescription": "Nomeie o serviço, escolha o destino da 
telemetria e gere as instruções exatas.",
+      "guided": {
+        "stage": {
+          "source": "Origem",
+          "service": "Serviço",
+          "destination": "Rota de entrada",
+          "guide": "Guia de configuração"
+        },
+        "serviceTitle": "A qual serviço esta telemetria pertence?",
+        "serviceDescription": "Estes valores se tornam atributos de recurso 
usados em consultas, filtros e correlações.",
+        "destinationTitle": "Para onde a telemetria deve ser enviada 
primeiro?",
+        "destinationDescription": "Escolha uma rota de entrada. Os detalhes de 
conexão aparecem apenas para a seleção atual.",
+        "guideTitle": "Gerar instruções de configuração",
+        "guideDescription": "Confirme o destino e a autorização para gerar as 
etapas deste serviço.",
+        "currentIntake": "Entrada atual",
+        "selectAvailableDestination": "Escolha uma rota disponível para ver os 
detalhes de conexão.",
+        "selectedDestination": "Rota de entrada",
+        "online": "Online",
+        "collectorId": "ID do Collector",
+        "destinationInstance": "Instância de entrada",
+        "copyEndpoint": "Copiar endpoint {{transport}}"
+      },
       "serviceContext": "Contexto do serviço",
       "installTitle": "Instalar e configurar",
       "recipeLabel": "Receita de instrumentação",
@@ -890,6 +911,15 @@
     "tokenMissing": "Não informado",
     "token": {
       "generateAccess": "Gerar token de acesso",
+      "create": "Criar token de acesso",
+      "useExisting": "Usar um token existente",
+      "cancelExisting": "Cancelar",
+      "setupTitle": "Crie um token para continuar",
+      "setupDescription": "Ele será adicionado automaticamente a esta 
configuração. Continue quando estiver pronto.",
+      "existingTitle": "Usar um token existente",
+      "existingDescription": "Cole um token somente se ele já tiver sido 
emitido para a ingestão OTLP.",
+      "readyTitle": "Token de acesso pronto",
+      "readyDescription": "Ele será inserido automaticamente no guia de 
configuração.",
       "generateTitle": "Gerar token de acesso",
       "generate": "Gerar",
       "name": "Nome do token",
@@ -900,6 +930,7 @@
       "notGenerated": "Nenhum token de acesso gerado",
       "generateError": "Não foi possível gerar o token de acesso.",
       "acknowledgementRequired": "Salve este token de exibição única antes de 
sair ou recomeçar. Ele existe apenas na memória desta página.",
+      "guideAcknowledgementRequired": "Este token de exibição única está 
visível no guia abaixo. Salve-o antes de sair ou recomeçar.",
       "acknowledge": "Salvei este token",
       "plaintextBearerWarning": "O token Bearer será transmitido por uma 
conexão não criptografada."
     },
@@ -945,6 +976,8 @@
     },
     "action": {
       "exit": "Voltar ao painel",
+      "previous": "Anterior",
+      "next": "Próximo",
       "continue": "Continuar",
       "render": "Gerar instruções",
       "reviewContext": "Revisar contexto",
@@ -1296,6 +1329,8 @@
     "selectedPauseConfirm_other": "Pausar {{count}} monitores selecionados?",
     "delete": "Excluir",
     "deleteConfirm": "Excluir os dados dos monitores selecionados?",
+    "selectAll": "Selecionar todos os monitores",
+    "selectOne": "Selecionar {{name}}",
     "selected": "{{count}} selecionados",
     "clearSelection": "Limpar seleção",
     "success": "Operação de monitor concluída.",
@@ -1372,6 +1407,9 @@
     "average": "Média",
     "samples": "Amostras",
     "refreshHistory": "Atualizar histórico",
+    "investigation": {
+      "open": "Sinais relacionados"
+    },
     "collapseHistory": "Recolher histórico",
     "expandHistory": "Expandir histórico",
     "layout": {
diff --git a/web-app/src/assets/i18n/zh-cn.json 
b/web-app/src/assets/i18n/zh-cn.json
index 96336ac501..0e45e59040 100644
--- a/web-app/src/assets/i18n/zh-cn.json
+++ b/web-app/src/assets/i18n/zh-cn.json
@@ -639,6 +639,27 @@
       "contextTitle": "服务与目标位置",
       "configureTitle": "配置",
       "configureDescription": "命名服务、选择遥测数据的目标位置,然后生成准确的接入步骤。",
+      "guided": {
+        "stage": {
+          "source": "接入来源",
+          "service": "服务信息",
+          "destination": "接入路径",
+          "guide": "接入指南"
+        },
+        "serviceTitle": "这批遥测数据属于哪个服务?",
+        "serviceDescription": "这些字段会写入遥测资源属性,用于查询、筛选和关联。",
+        "destinationTitle": "遥测数据先发到哪里?",
+        "destinationDescription": "先选一条接入路径。地址和连接状态只在当前选择中展开。",
+        "guideTitle": "生成接入说明",
+        "guideDescription": "确认目标位置和授权方式,然后生成适用于当前服务的配置步骤。",
+        "currentIntake": "当前接入",
+        "selectAvailableDestination": "选择一个可用的接入路径后,这里会显示连接地址。",
+        "selectedDestination": "接入路径",
+        "online": "在线",
+        "collectorId": "Collector ID",
+        "destinationInstance": "接入实例",
+        "copyEndpoint": "复制 {{transport}} 地址"
+      },
       "serviceContext": "服务上下文",
       "installTitle": "安装并配置",
       "recipeLabel": "插桩方案",
@@ -879,6 +900,15 @@
     "tokenMissing": "未提供",
     "token": {
       "generateAccess": "生成访问令牌",
+      "create": "创建访问令牌",
+      "useExisting": "使用已有令牌",
+      "cancelExisting": "取消",
+      "setupTitle": "创建令牌后继续",
+      "setupDescription": "令牌会自动加入本次接入配置,创建完成后即可进入下一步。",
+      "existingTitle": "使用已有令牌",
+      "existingDescription": "仅在已有 OTLP 接入令牌时粘贴。",
+      "readyTitle": "访问令牌已准备好",
+      "readyDescription": "下一步会自动将它写入接入指南。",
       "generateTitle": "生成访问令牌",
       "generate": "生成",
       "name": "令牌名称",
@@ -889,6 +919,7 @@
       "notGenerated": "尚未生成访问令牌",
       "generateError": "无法生成访问令牌。",
       "acknowledgementRequired": "请在退出或重新开始前保存此一次性令牌。它仅存在于当前页面的内存中。",
+      "guideAcknowledgementRequired": "一次性令牌已写入下方接入指南,请在退出或重新开始前保存。",
       "acknowledge": "我已保存此令牌",
       "plaintextBearerWarning": "Bearer 令牌将通过未加密连接传输。"
     },
@@ -934,6 +965,8 @@
     },
     "action": {
       "exit": "返回仪表盘",
+      "previous": "上一步",
+      "next": "下一步",
       "continue": "继续",
       "render": "生成操作说明",
       "reviewContext": "检查上下文",
@@ -1280,6 +1313,8 @@
     "selectedPauseConfirm_other": "确认暂停所选 {{count}} 项监控?",
     "delete": "删除",
     "deleteConfirm": "确认删除所选监控数据?",
+    "selectAll": "选择全部监控",
+    "selectOne": "选择 {{name}}",
     "selected": "已选择 {{count}} 项",
     "clearSelection": "清除选择",
     "success": "监控操作已完成。",
@@ -1356,6 +1391,9 @@
     "average": "平均值",
     "samples": "样本数",
     "refreshHistory": "刷新历史趋势",
+    "investigation": {
+      "open": "关联信号"
+    },
     "collapseHistory": "收起历史趋势",
     "expandHistory": "展开历史趋势",
     "layout": {
diff --git a/web-app/src/assets/i18n/zh-tw.json 
b/web-app/src/assets/i18n/zh-tw.json
index a7c316c92c..f3203cc4f3 100644
--- a/web-app/src/assets/i18n/zh-tw.json
+++ b/web-app/src/assets/i18n/zh-tw.json
@@ -639,6 +639,27 @@
       "contextTitle": "服務與目的地",
       "configureTitle": "設定",
       "configureDescription": "命名服務、選擇遙測資料的目的地,然後產生準確的接入步驟。",
+      "guided": {
+        "stage": {
+          "source": "接入來源",
+          "service": "服務資訊",
+          "destination": "接入路徑",
+          "guide": "接入指南"
+        },
+        "serviceTitle": "這批遙測資料屬於哪個服務?",
+        "serviceDescription": "這些欄位會寫入遙測資源屬性,用於查詢、篩選和關聯。",
+        "destinationTitle": "遙測資料先傳到哪裡?",
+        "destinationDescription": "先選一條接入路徑。位址和連線狀態只在目前選擇中展開。",
+        "guideTitle": "產生接入說明",
+        "guideDescription": "確認目的地和授權方式,然後產生適用於目前服務的設定步驟。",
+        "currentIntake": "目前接入",
+        "selectAvailableDestination": "選擇一個可用的接入路徑後,這裡會顯示連線位址。",
+        "selectedDestination": "接入路徑",
+        "online": "在線",
+        "collectorId": "Collector ID",
+        "destinationInstance": "接入實例",
+        "copyEndpoint": "複製 {{transport}} 位址"
+      },
       "serviceContext": "服務上下文",
       "installTitle": "安裝並設定",
       "recipeLabel": "插樁方案",
@@ -879,6 +900,15 @@
     "tokenMissing": "未提供",
     "token": {
       "generateAccess": "產生存取權杖",
+      "create": "建立存取權杖",
+      "useExisting": "使用現有權杖",
+      "cancelExisting": "取消",
+      "setupTitle": "建立權杖後繼續",
+      "setupDescription": "權杖會自動加入本次接入設定,建立完成後即可進入下一步。",
+      "existingTitle": "使用現有權杖",
+      "existingDescription": "僅在已有 OTLP 接入權杖時貼上。",
+      "readyTitle": "存取權杖已準備好",
+      "readyDescription": "下一步會自動將它寫入接入指南。",
       "generateTitle": "產生存取權杖",
       "generate": "產生",
       "name": "權杖名稱",
@@ -889,6 +919,7 @@
       "notGenerated": "尚未產生存取權杖",
       "generateError": "無法產生存取權杖。",
       "acknowledgementRequired": "請在離開或重新開始前儲存此一次性權杖。它只存在於目前頁面的記憶體中。",
+      "guideAcknowledgementRequired": "一次性權杖已寫入下方接入指南,請在離開或重新開始前儲存。",
       "acknowledge": "我已儲存此權杖",
       "plaintextBearerWarning": "Bearer 權杖將透過未加密連線傳輸。"
     },
@@ -934,6 +965,8 @@
     },
     "action": {
       "exit": "返回儀表板",
+      "previous": "上一步",
+      "next": "下一步",
       "continue": "繼續",
       "render": "產生操作說明",
       "reviewContext": "檢查情境",
@@ -1280,6 +1313,8 @@
     "selectedPauseConfirm_other": "確認暫停所選 {{count}} 項監控?",
     "delete": "刪除",
     "deleteConfirm": "確認刪除所選監控資料?",
+    "selectAll": "選擇全部監控",
+    "selectOne": "選擇 {{name}}",
     "selected": "已選擇 {{count}} 項",
     "clearSelection": "清除選取",
     "success": "監控操作已完成。",
@@ -1356,6 +1391,9 @@
     "average": "平均值",
     "samples": "樣本數",
     "refreshHistory": "重新整理歷史趨勢",
+    "investigation": {
+      "open": "關聯訊號"
+    },
     "collapseHistory": "收合歷史趨勢",
     "expandHistory": "展開歷史趨勢",
     "layout": {
diff --git 
a/web-app/src/features/instrumentation/components/instrumentation-configure-step-props.ts
 
b/web-app/src/features/instrumentation/components/instrumentation-configure-step-props.ts
new file mode 100644
index 0000000000..08f7e0fdc2
--- /dev/null
+++ 
b/web-app/src/features/instrumentation/components/instrumentation-configure-step-props.ts
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0.
+ */
+
+import type { AccessTokenGenerationDraft } from 
'@/shared/access-token/access-token-generation-model';
+
+import type { InstrumentationConfigurePhase } from 
'../model/instrumentation-guided-flow';
+import type { IntakeProfilesResponse, ServiceIdentity } from 
'../model/instrumentation-v2-contract';
+
+export type ConfigureStepProps = {
+  phase: InstrumentationConfigurePhase;
+  profiles: IntakeProfilesResponse;
+  profileId: string;
+  service: ServiceIdentity;
+  platform?: string | undefined;
+  platformOptions: string[];
+  canRender: boolean;
+  rendering: boolean;
+  renderError: boolean;
+  token: string;
+  tokenDraft?: AccessTokenGenerationDraft | undefined;
+  tokenGenerating: boolean;
+  tokenError: boolean;
+  tokenAcknowledgementRequired: boolean;
+  requiresToken: boolean;
+  canGenerateToken: boolean;
+  onProfile: (intakeProfileId: string) => void;
+  onService: (patch: Partial<ServiceIdentity>) => void;
+  onPlatform: (platform: string) => void;
+  onToken: (token: string) => void;
+  onRender: () => void;
+  onPrevious: () => void;
+  onNext: () => void;
+  onOpenToken: () => void;
+  onCloseToken: () => void;
+  onTokenDraft: (draft: AccessTokenGenerationDraft) => void;
+  onGenerateToken: () => void;
+  onAcknowledgeToken: () => void;
+};
diff --git 
a/web-app/src/features/instrumentation/components/instrumentation-configure-step.tsx
 
b/web-app/src/features/instrumentation/components/instrumentation-configure-step.tsx
index a25a128124..cc56840e5b 100644
--- 
a/web-app/src/features/instrumentation/components/instrumentation-configure-step.tsx
+++ 
b/web-app/src/features/instrumentation/components/instrumentation-configure-step.tsx
@@ -8,157 +8,93 @@
 import { Alert, Button, Typography } from 'antd';
 import { useTranslation } from 'react-i18next';
 
-import type { AccessTokenGenerationDraft } from 
'@/shared/access-token/access-token-generation-model';
-
-import { profileUsesPlaintext } from '../model/intake-profile';
-import type { IntakeProfilesResponse, ServiceIdentity } from 
'../model/instrumentation-v2-contract';
-import { InstrumentationAccessTokenModal } from 
'./instrumentation-access-token-modal';
-import { InstrumentationPlatformField, InstrumentationTokenField } from 
'./instrumentation-configuration-fields';
+import { serviceConfigurationReady } from 
'../model/instrumentation-guided-flow';
+import { InstrumentationPlatformField } from 
'./instrumentation-configuration-fields';
 import styles from './instrumentation-configure.module.css';
+import type { ConfigureStepProps } from 
'./instrumentation-configure-step-props';
 import { InstrumentationDestinationSelector } from 
'./instrumentation-destination-selector';
+import { InstrumentationGuideConfiguration, InstrumentationTokenModal } from 
'./instrumentation-guide-configuration';
 import { InstrumentationServiceIdentityFields } from 
'./instrumentation-service-identity-fields';
 
-type ConfigureStepProps = {
-  profiles: IntakeProfilesResponse;
-  profileId: string;
-  service: ServiceIdentity;
-  platform?: string | undefined;
-  platformOptions: string[];
-  canRender: boolean;
-  rendering: boolean;
-  renderError: boolean;
-  token: string;
-  tokenDraft?: AccessTokenGenerationDraft | undefined;
-  tokenGenerating: boolean;
-  tokenError: boolean;
-  tokenAcknowledgementRequired: boolean;
-  requiresToken: boolean;
-  canGenerateToken: boolean;
-  onProfile: (intakeProfileId: string) => void;
-  onService: (patch: Partial<ServiceIdentity>) => void;
-  onPlatform: (platform: string) => void;
-  onToken: (token: string) => void;
-  onRender: () => void;
-  onOpenToken: () => void;
-  onCloseToken: () => void;
-  onTokenDraft: (draft: AccessTokenGenerationDraft) => void;
-  onGenerateToken: () => void;
-  onAcknowledgeToken: () => void;
-};
-
 export function InstrumentationConfigureStep(props: ConfigureStepProps) {
   const { t } = useTranslation();
+  const headingKey = `instrumentation.v2.guided.${props.phase}Title`;
+  const descriptionKey = `instrumentation.v2.guided.${props.phase}Description`;
   return (
     <section className={styles.section} 
aria-labelledby="instrumentation-configure-title">
       <div className={styles.configureIntro}>
         <Typography.Title id="instrumentation-configure-title" level={3}>
-          {t('instrumentation.v2.configureTitle')}
+          {t(headingKey)}
         </Typography.Title>
-        <Typography.Text 
type="secondary">{t('instrumentation.v2.configureDescription')}</Typography.Text>
+        <Typography.Text type="secondary">{t(descriptionKey)}</Typography.Text>
       </div>
-      {props.profiles.status !== 'available' && (
+      {props.phase === 'destination' && props.profiles.status !== 'available' 
&& (
         <Alert
           type={props.profiles.status === 'unavailable' ? 'error' : 'warning'}
           showIcon
           message={t(`instrumentation.v2.profile.${props.profiles.status}`)}
         />
       )}
-      <div className={styles.configureWorkspace}>
-        <InstrumentationDestinationSelector
-          profiles={props.profiles}
-          profileId={props.profileId}
-          onProfile={props.onProfile}
-        />
-        <div className={styles.configurePrimary}>
-          <section className={styles.configureGroup} 
aria-labelledby="instrumentation-service-context-title">
-            <Typography.Title id="instrumentation-service-context-title" 
level={5}>
-              {t('instrumentation.v2.serviceContext')}
-            </Typography.Title>
-            <InstrumentationServiceIdentityFields service={props.service} 
onService={props.onService} />
-            <InstrumentationPlatformField
-              platform={props.platform}
-              options={props.platformOptions}
-              onPlatform={props.onPlatform}
-            />
-          </section>
-          <TokenConfiguration {...props} />
-        </div>
-      </div>
-      <div className={styles.configureActions}>
-        <TokenActions {...props} />
-        <Button type="primary" disabled={!props.canRender} 
loading={props.rendering} onClick={props.onRender}>
-          {t('instrumentation.action.render')}
-        </Button>
-      </div>
-      {props.renderError && <Alert type="error" showIcon 
message={t('instrumentation.v2.renderError')} />}
-      <TokenModal {...props} />
+      {props.phase === 'service' && <ServiceStep {...props} />}
+      {props.phase === 'destination' && <DestinationStep {...props} />}
+      {props.phase === 'guide' && <InstrumentationGuideConfiguration 
{...props} />}
+      <InstrumentationTokenModal {...props} />
     </section>
   );
 }
 
-function TokenConfiguration(props: ConfigureStepProps) {
+function ServiceStep(props: ConfigureStepProps) {
   const { t } = useTranslation();
-  if (!props.requiresToken && !props.tokenAcknowledgementRequired) return null;
+  const ready = serviceConfigurationReady(props.service, props.platform, 
props.platformOptions);
   return (
     <>
-      {props.tokenAcknowledgementRequired && (
-        <Alert
-          type="warning"
-          showIcon
-          message={t('instrumentation.token.acknowledgementRequired')}
-          action={
-            <Button size="small" onClick={props.onAcknowledgeToken}>
-              {t('instrumentation.token.acknowledge')}
-            </Button>
-          }
-        />
-      )}
-      {props.requiresToken && selectedProfileUsesPlaintext(props.profiles, 
props.profileId) && (
-        <Alert type="warning" showIcon 
message={t('instrumentation.token.plaintextBearerWarning')} />
-      )}
-      {props.requiresToken && (
-        <InstrumentationTokenField
-          token={props.token}
-          disabled={props.tokenAcknowledgementRequired}
-          onToken={props.onToken}
+      <section className={styles.configureGroup} 
aria-labelledby="instrumentation-service-context-title">
+        <Typography.Title id="instrumentation-service-context-title" level={5}>
+          {t('instrumentation.v2.serviceContext')}
+        </Typography.Title>
+        <InstrumentationServiceIdentityFields service={props.service} 
onService={props.onService} />
+        <InstrumentationPlatformField
+          platform={props.platform}
+          options={props.platformOptions}
+          onPlatform={props.onPlatform}
         />
-      )}
+      </section>
+      <StepActions onPrevious={props.onPrevious}>
+        <Button type="primary" disabled={!ready} onClick={props.onNext}>
+          {t('instrumentation.action.next')}
+        </Button>
+      </StepActions>
     </>
   );
 }
 
-function TokenActions(props: ConfigureStepProps) {
+function DestinationStep(props: ConfigureStepProps) {
   const { t } = useTranslation();
-  if (!props.requiresToken) return null;
+  const available = props.profiles.profiles.some(
+    profile => profile.id === props.profileId && profile.availability === 
'available'
+  );
   return (
     <>
-      {props.canGenerateToken && (
-        <Button disabled={props.tokenAcknowledgementRequired} 
onClick={props.onOpenToken}>
-          {t('instrumentation.token.generateAccess')}
+      <InstrumentationDestinationSelector
+        profiles={props.profiles}
+        profileId={props.profileId}
+        onProfile={props.onProfile}
+      />
+      <StepActions onPrevious={props.onPrevious}>
+        <Button type="primary" disabled={!available} onClick={props.onNext}>
+          {t('instrumentation.action.next')}
         </Button>
-      )}
-      <Typography.Text type={props.token ? 'success' : 'secondary'}>
-        {t(props.token ? 'instrumentation.token.ready' : 
'instrumentation.token.notGenerated')}
-      </Typography.Text>
+      </StepActions>
     </>
   );
 }
 
-function TokenModal(props: ConfigureStepProps) {
-  if (!props.canGenerateToken || !props.tokenDraft) return null;
+function StepActions(props: { onPrevious: () => void; children: 
React.ReactNode }) {
+  const { t } = useTranslation();
   return (
-    <InstrumentationAccessTokenModal
-      draft={props.tokenDraft}
-      tokenGenerating={props.tokenGenerating}
-      tokenError={props.tokenError}
-      onClose={props.onCloseToken}
-      onDraft={props.onTokenDraft}
-      onGenerate={props.onGenerateToken}
-    />
+    <div className={styles.configureActions}>
+      <Button 
onClick={props.onPrevious}>{t('instrumentation.action.previous')}</Button>
+      {props.children}
+    </div>
   );
 }
-
-function selectedProfileUsesPlaintext(profiles: IntakeProfilesResponse, 
profileId: string) {
-  const profile = profiles.profiles.find(item => item.id === profileId);
-  return profileUsesPlaintext(profile);
-}
diff --git 
a/web-app/src/features/instrumentation/components/instrumentation-configure.module.css
 
b/web-app/src/features/instrumentation/components/instrumentation-configure.module.css
index 2ebf909215..09f6471a60 100644
--- 
a/web-app/src/features/instrumentation/components/instrumentation-configure.module.css
+++ 
b/web-app/src/features/instrumentation/components/instrumentation-configure.module.css
@@ -1,7 +1,9 @@
 .section {
-  width: min(100%, 1120px);
+  width: min(100%, 1060px);
+  min-height: calc(100dvh - 152px);
   display: grid;
-  align-content: start;
+  grid-template-rows: auto auto minmax(0, 1fr) auto;
+  align-content: stretch;
   gap: 24px;
 }
 
@@ -11,10 +13,7 @@
 }
 
 .configureIntro,
-.configurePrimary,
-.configureGroup,
-.destinationPanel,
-.destinationHeading {
+.configureGroup {
   display: grid;
   align-content: start;
 }
@@ -34,18 +33,12 @@
   gap: 28px;
 }
 
-.configurePrimary {
-  gap: 24px;
-}
-
-.configureGroup,
-.destinationPanel,
-.destinationHeading {
+.configureGroup {
   gap: 12px;
 }
 
-.destinationHeading {
-  gap: 4px;
+.configureGroup {
+  width: min(100%, 720px);
 }
 
 .serviceNameField {
@@ -75,102 +68,12 @@
   margin-top: 10px;
 }
 
-.destinationList {
-  display: grid;
-  grid-template-columns: 1fr;
-  border-top: 1px solid var(--hb-border);
-}
-
-.destinationOption {
-  min-height: 96px;
-  display: grid;
-  grid-template-columns: minmax(230px, 0.85fr) minmax(260px, 1.15fr) 
minmax(250px, 1fr);
-  align-items: center;
-  gap: 20px;
-  border: 0;
-  border-bottom: 1px solid var(--hb-border);
-  padding: 14px 12px;
-  background: transparent;
-  color: inherit;
-  text-align: left;
-}
-
-.destinationOption:not(:disabled) {
-  cursor: pointer;
-}
-
-.destinationOption:disabled {
-  cursor: not-allowed;
-  color: var(--hb-text-secondary);
-}
-
-.destinationOption:hover:not(:disabled),
-.destinationOption:focus-visible {
-  background: var(--hb-bg-hover);
-}
-
-.destinationOption:focus-visible {
-  outline: 2px solid var(--hb-focus-ring);
-  outline-offset: -2px;
-}
-
-.destinationOptionSelected {
-  box-shadow: inset 2px 0 0 var(--hb-brand-accent);
-  background: var(--hb-bg-selected);
-}
-
-.destinationIdentity,
-.destinationExplanation,
-.destinationEndpointList {
-  display: grid;
-  align-content: center;
-  gap: 6px;
-  min-width: 0;
-}
-
-.destinationIdentity {
-  grid-template-columns: minmax(0, 1fr) auto;
-}
-
-.destinationRoute {
-  grid-column: 1 / -1;
-  color: var(--hb-text-secondary);
-}
-
-.destinationAvailability {
-  grid-column: 2;
-  grid-row: 1;
-  justify-self: end;
-}
-
-.destinationExplanation small,
-.destinationEndpointList small,
-.destinationSetupHint {
-  color: var(--hb-text-secondary);
-}
-
-.endpointEvidence {
-  display: grid;
-  grid-template-columns: minmax(0, auto) auto minmax(0, 1fr);
-  align-items: center;
-  gap: 6px;
-}
-
-.destinationSetupHint {
-  align-self: center;
-}
-
-.endpointEvidence code {
-  overflow: hidden;
-  color: var(--hb-text-secondary);
-  text-overflow: ellipsis;
-  white-space: nowrap;
-}
-
 .configureActions {
   display: flex;
   align-items: center;
   gap: 10px;
+  margin-top: auto;
+  align-self: end;
   border-top: 1px solid var(--hb-border);
   padding-top: 16px;
 }
@@ -190,17 +93,6 @@
   margin-top: 12px;
 }
 
-@media (max-width: 920px) {
-  .destinationOption {
-    grid-template-columns: minmax(210px, 0.8fr) minmax(0, 1.2fr);
-  }
-
-  .destinationEndpointList,
-  .destinationSetupHint {
-    grid-column: 1 / -1;
-  }
-}
-
 @media (max-width: 760px) {
   .serviceNameField {
     grid-template-columns: 1fr;
@@ -209,14 +101,4 @@
   .serviceNameField > :nth-child(3) {
     grid-column: 1;
   }
-
-  .destinationOption {
-    grid-template-columns: 1fr;
-    gap: 10px;
-  }
-
-  .destinationEndpointList,
-  .destinationSetupHint {
-    grid-column: 1;
-  }
 }
diff --git 
a/web-app/src/features/instrumentation/components/instrumentation-destination-selector.module.css
 
b/web-app/src/features/instrumentation/components/instrumentation-destination-selector.module.css
new file mode 100644
index 0000000000..6200d6a035
--- /dev/null
+++ 
b/web-app/src/features/instrumentation/components/instrumentation-destination-selector.module.css
@@ -0,0 +1,170 @@
+.destinationWorkspace {
+  display: grid;
+  grid-template-columns: minmax(0, 1fr) 300px;
+  align-items: stretch;
+  gap: 32px;
+}
+
+.destinationPanel {
+  display: grid;
+  align-content: start;
+  gap: 12px;
+}
+
+.destinationList {
+  display: grid;
+  border-top: 1px solid var(--hb-border);
+}
+
+.destinationOption {
+  position: relative;
+  min-height: 88px;
+  display: grid;
+  grid-template-columns: 20px minmax(220px, 0.9fr) minmax(240px, 1.1fr);
+  align-items: center;
+  gap: 14px;
+  border: 0;
+  border-bottom: 1px solid var(--hb-border);
+  padding: 14px 12px;
+  background: transparent;
+  color: inherit;
+  text-align: left;
+}
+
+.destinationOption:not(:disabled) {
+  cursor: pointer;
+}
+.destinationOption:disabled {
+  cursor: not-allowed;
+  color: var(--hb-text-secondary);
+}
+.destinationOption:hover:not(:disabled),
+.destinationOption:focus-visible {
+  background: var(--hb-bg-hover);
+}
+.destinationOption:focus-visible {
+  outline: 2px solid var(--hb-focus-ring);
+  outline-offset: -2px;
+}
+.destinationOptionSelected {
+  box-shadow: inset 2px 0 0 var(--hb-brand-accent);
+  background: var(--hb-bg-selected);
+}
+
+.destinationRadio {
+  width: 16px;
+  height: 16px;
+  display: grid;
+  place-items: center;
+  border: 1px solid var(--ant-color-border);
+  border-radius: 50%;
+  background: var(--hb-bg-raised);
+}
+
+.destinationRadio span {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  background: var(--hb-brand-accent);
+}
+.destinationIdentity,
+.destinationExplanation {
+  display: grid;
+  align-content: center;
+  gap: 6px;
+  min-width: 0;
+}
+.destinationIdentity {
+  grid-template-columns: minmax(0, 1fr) auto;
+}
+.destinationRoute {
+  grid-column: 1 / -1;
+  color: var(--hb-text-secondary);
+}
+.destinationAvailability {
+  grid-column: 2;
+  grid-row: 1;
+  justify-self: end;
+}
+.destinationExplanation,
+.destinationReason {
+  color: var(--hb-text-secondary);
+}
+.destinationReason {
+  grid-column: 2 / -1;
+  margin-top: -8px;
+  padding-bottom: 4px;
+}
+
+.currentIntake {
+  min-width: 0;
+  display: grid;
+  align-content: start;
+  gap: 14px;
+  border-left: 1px solid var(--hb-border);
+  padding-left: 28px;
+}
+
+.currentIntake h5 {
+  margin: 0;
+}
+.currentIntakeStatus {
+  display: flex;
+  align-items: center;
+  gap: 7px;
+  color: var(--ant-color-success);
+}
+.currentIntakeMeta {
+  display: grid;
+  gap: 4px;
+  color: var(--hb-text-secondary);
+}
+.currentIntakeMeta code,
+.endpointSummary code {
+  overflow-wrap: anywhere;
+  color: var(--hb-text-primary);
+}
+.endpointSummary {
+  display: grid;
+  gap: 16px;
+  margin: 2px 0 0;
+}
+.endpointSummary > div {
+  display: grid;
+  gap: 5px;
+}
+.endpointSummary dt {
+  color: var(--hb-text-secondary);
+  font-size: 12px;
+}
+.endpointSummary dd {
+  min-width: 0;
+  display: grid;
+  grid-template-columns: minmax(0, 1fr) auto auto;
+  align-items: center;
+  gap: 6px;
+  margin: 0;
+}
+
+@media (max-width: 920px) {
+  .destinationWorkspace {
+    grid-template-columns: 1fr;
+  }
+  .currentIntake {
+    border-top: 1px solid var(--hb-border);
+    border-left: 0;
+    padding-top: 20px;
+    padding-left: 0;
+  }
+}
+
+@media (max-width: 760px) {
+  .destinationOption {
+    grid-template-columns: 20px minmax(0, 1fr);
+    gap: 8px 12px;
+  }
+  .destinationExplanation,
+  .destinationReason {
+    grid-column: 2;
+  }
+}
diff --git 
a/web-app/src/features/instrumentation/components/instrumentation-destination-selector.tsx
 
b/web-app/src/features/instrumentation/components/instrumentation-destination-selector.tsx
index d65febaeb7..93833855c4 100644
--- 
a/web-app/src/features/instrumentation/components/instrumentation-destination-selector.tsx
+++ 
b/web-app/src/features/instrumentation/components/instrumentation-destination-selector.tsx
@@ -5,7 +5,8 @@
  * The ASF licenses this file to You under the Apache License, Version 2.0.
  */
 
-import { Tag, Typography } from 'antd';
+import { CheckCircleFilled, CopyOutlined } from '@ant-design/icons';
+import { Button, Select, Tag, Typography } from 'antd';
 import { useTranslation } from 'react-i18next';
 
 import {
@@ -15,7 +16,7 @@ import {
   type IntakeProfileKind
 } from '../model/intake-profile';
 import type { IntakeProfile, IntakeProfilesResponse } from 
'../model/instrumentation-v2-contract';
-import styles from './instrumentation-configure.module.css';
+import styles from './instrumentation-destination-selector.module.css';
 
 export function InstrumentationDestinationSelector(props: {
   profiles: IntakeProfilesResponse;
@@ -23,29 +24,31 @@ export function InstrumentationDestinationSelector(props: {
   onProfile: (intakeProfileId: string) => void;
 }) {
   const { t } = useTranslation();
+  const selectedProfile = props.profiles.profiles.find(profile => profile.id 
=== props.profileId);
   return (
-    <section className={styles.destinationPanel} 
aria-labelledby="instrumentation-destination-title">
-      <div className={styles.destinationHeading}>
-        <Typography.Title id="instrumentation-destination-title" level={5}>
-          {t('instrumentation.v2.destination')}
-        </Typography.Title>
-        <Typography.Text 
type="secondary">{t('instrumentation.v2.destinationDescription')}</Typography.Text>
-      </div>
-      <div className={styles.destinationList}>
-        {INTAKE_PROFILE_KINDS.flatMap(kind => {
-          const profiles = profilesForKind(props.profiles, kind);
-          if (!profiles.length) return <MissingDestination key={kind} 
kind={kind} />;
-          return profiles.map(profile => (
-            <DestinationOption
-              key={profile.id}
-              profile={profile}
-              selected={props.profileId === profile.id}
-              onSelect={props.onProfile}
-            />
-          ));
-        })}
-      </div>
-    </section>
+    <div className={styles.destinationWorkspace}>
+      <section className={styles.destinationPanel} 
aria-label={t('instrumentation.v2.destination')}>
+        <div className={styles.destinationList}>
+          {INTAKE_PROFILE_KINDS.map(kind => {
+            const kindProfiles = profilesForKind(props.profiles, kind);
+            if (!kindProfiles.length) return <MissingDestination key={kind} 
kind={kind} />;
+            const profile =
+              kindProfiles.find(item => item.id === props.profileId) ??
+              kindProfiles.find(item => item.availability === 'available') ??
+              kindProfiles[0]!;
+            return (
+              <DestinationOption
+                key={kind}
+                profile={profile}
+                selected={selectedProfile?.kind === kind}
+                onSelect={props.onProfile}
+              />
+            );
+          })}
+        </div>
+      </section>
+      <CurrentIntake profile={selectedProfile} profiles={props.profiles} 
onProfile={props.onProfile} />
+    </div>
   );
 }
 
@@ -64,20 +67,12 @@ function DestinationOption(props: {
       aria-pressed={props.selected}
       onClick={() => props.onSelect(props.profile.id)}
     >
+      <span className={styles.destinationRadio} aria-hidden="true">
+        {props.selected && <span />}
+      </span>
       <DestinationIdentity kind={props.profile.kind} available={available} />
       <DestinationExplanation kind={props.profile.kind} />
-      <span className={styles.destinationEndpointList}>
-        {intakeEndpointEntries(props.profile).map(([transport, endpoint]) => (
-          <span key={transport} className={styles.endpointEvidence}>
-            <span>{t(`instrumentation.v2.transport.${transport}`)}</span>
-            <Tag color={endpoint.security === 'plaintext' ? 'error' : 
'success'}>
-              {t(`instrumentation.v2.security.${endpoint.security}`)}
-            </Tag>
-            <code>{endpoint.url}</code>
-          </span>
-        ))}
-        {!available && 
<small>{t(profileReasonKey(props.profile.errorCode))}</small>}
-      </span>
+      {!available && <small 
className={styles.destinationReason}>{t(profileReasonKey(props.profile.errorCode))}</small>}
     </button>
   );
 }
@@ -86,9 +81,10 @@ function MissingDestination({ kind }: { kind: 
IntakeProfileKind }) {
   const { t } = useTranslation();
   return (
     <button type="button" className={styles.destinationOption} disabled 
aria-pressed={false}>
+      <span className={styles.destinationRadio} aria-hidden="true" />
       <DestinationIdentity kind={kind} available={false} />
       <DestinationExplanation kind={kind} />
-      <small 
className={styles.destinationSetupHint}>{t(setupHintKey(kind))}</small>
+      <small 
className={styles.destinationReason}>{t(setupHintKey(kind))}</small>
     </button>
   );
 }
@@ -111,12 +107,84 @@ function DestinationExplanation({ kind }: { kind: 
IntakeProfileKind }) {
   return (
     <span className={styles.destinationExplanation}>
       <span>{t(`instrumentation.v2.profilePurpose.${kind}`)}</span>
-      <small>{t(`instrumentation.v2.profileOwnership.${kind}`)}</small>
-      <small>{t(`instrumentation.v2.profileBoundary.${kind}`)}</small>
     </span>
   );
 }
 
+function CurrentIntake(props: {
+  profile: IntakeProfile | undefined;
+  profiles: IntakeProfilesResponse;
+  onProfile: (profileId: string) => void;
+}) {
+  const { t } = useTranslation();
+  const availablePeers = props.profile
+    ? profilesForKind(props.profiles, props.profile.kind).filter(item => 
item.availability === 'available')
+    : [];
+  return (
+    <aside className={styles.currentIntake} 
aria-label={t('instrumentation.v2.guided.currentIntake')}>
+      <Typography.Title 
level={5}>{t('instrumentation.v2.guided.currentIntake')}</Typography.Title>
+      {!props.profile || props.profile.availability !== 'available' ? (
+        <Typography.Text 
type="secondary">{t('instrumentation.v2.guided.selectAvailableDestination')}</Typography.Text>
+      ) : (
+        <>
+          <div className={styles.currentIntakeStatus}>
+            <CheckCircleFilled aria-hidden="true" />
+            <span>{t('instrumentation.v2.guided.online')}</span>
+          </div>
+          
<strong>{t(`instrumentation.v2.profileKind.${props.profile.kind}`)}</strong>
+          {availablePeers.length > 1 && (
+            <Select
+              aria-label={t('instrumentation.v2.guided.destinationInstance')}
+              value={props.profile.id}
+              options={availablePeers.map(item => ({ value: item.id, label: 
item.collectorId ?? item.id }))}
+              onChange={props.onProfile}
+            />
+          )}
+          {props.profile.collectorId && (
+            <span className={styles.currentIntakeMeta}>
+              <span>{t('instrumentation.v2.guided.collectorId')}</span>
+              <code>{props.profile.collectorId}</code>
+            </span>
+          )}
+          <EndpointSummary profile={props.profile} />
+        </>
+      )}
+    </aside>
+  );
+}
+
+function EndpointSummary({ profile }: { profile: IntakeProfile }) {
+  const { t } = useTranslation();
+  return (
+    <dl className={styles.endpointSummary}>
+      {intakeEndpointEntries(profile).map(([transport, endpoint]) => (
+        <div key={transport}>
+          <dt>{t(`instrumentation.v2.transport.${transport}`)}</dt>
+          <dd>
+            <code>{endpoint.url}</code>
+            <Tag color={endpoint.security === 'plaintext' ? 'warning' : 
'success'}>
+              {t(`instrumentation.v2.security.${endpoint.security}`)}
+            </Tag>
+            <Button
+              type="text"
+              size="small"
+              icon={<CopyOutlined aria-hidden="true" />}
+              aria-label={t('instrumentation.v2.guided.copyEndpoint', {
+                transport: t(`instrumentation.v2.transport.${transport}`)
+              })}
+              onClick={() => copyEndpoint(endpoint.url)}
+            />
+          </dd>
+        </div>
+      ))}
+    </dl>
+  );
+}
+
+function copyEndpoint(value: string) {
+  void navigator.clipboard?.writeText(value).catch(() => undefined);
+}
+
 function setupHintKey(kind: IntakeProfileKind) {
   if (kind === 'server') return 'instrumentation.v2.serverSetupHint';
   if (kind === 'hertzbeat_collector') return 
'instrumentation.v2.hybridCollectorSetupHint';
diff --git 
a/web-app/src/features/instrumentation/components/instrumentation-guide-configuration.module.css
 
b/web-app/src/features/instrumentation/components/instrumentation-guide-configuration.module.css
new file mode 100644
index 0000000000..665aa89f80
--- /dev/null
+++ 
b/web-app/src/features/instrumentation/components/instrumentation-guide-configuration.module.css
@@ -0,0 +1,68 @@
+.guidePreparation {
+  width: min(100%, 760px);
+  display: grid;
+  gap: 20px;
+}
+
+.tokenSetup {
+  display: grid;
+  gap: 14px;
+}
+.tokenSetupPrompt {
+  display: grid;
+  grid-template-columns: minmax(0, 1fr) auto;
+  align-items: center;
+  gap: 20px;
+  padding: 4px 0;
+}
+.tokenSetupCopy {
+  display: grid;
+  gap: 3px;
+}
+.tokenSetupActions {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+.cancelExistingToken {
+  width: fit-content;
+  padding-inline: 0;
+}
+
+.selectedDestinationInline {
+  display: grid;
+  grid-template-columns: auto minmax(0, 1fr);
+  gap: 4px 14px;
+  border-bottom: 1px solid var(--hb-border);
+  padding-bottom: 16px;
+}
+
+.selectedDestinationInline > span,
+.selectedDestinationInline small {
+  color: var(--hb-text-secondary);
+}
+.selectedDestinationInline small {
+  grid-column: 2;
+}
+.selectedDestinationEndpoints {
+  grid-column: 2;
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px 16px;
+}
+.selectedDestinationEndpoints > span {
+  display: flex;
+  gap: 6px;
+  font-size: 12px;
+}
+
+@media (max-width: 760px) {
+  .tokenSetupPrompt {
+    grid-template-columns: 1fr;
+    gap: 12px;
+  }
+  .tokenSetupActions {
+    align-items: flex-start;
+    flex-direction: column;
+  }
+}
diff --git 
a/web-app/src/features/instrumentation/components/instrumentation-guide-configuration.tsx
 
b/web-app/src/features/instrumentation/components/instrumentation-guide-configuration.tsx
new file mode 100644
index 0000000000..b240f30a5b
--- /dev/null
+++ 
b/web-app/src/features/instrumentation/components/instrumentation-guide-configuration.tsx
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0.
+ */
+
+import { Alert, Button, Typography } from 'antd';
+import { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+
+import { intakeEndpointEntries, profileUsesPlaintext } from 
'../model/intake-profile';
+import { InstrumentationAccessTokenModal } from 
'./instrumentation-access-token-modal';
+import { InstrumentationTokenField } from 
'./instrumentation-configuration-fields';
+import configureStyles from './instrumentation-configure.module.css';
+import type { ConfigureStepProps } from 
'./instrumentation-configure-step-props';
+import styles from './instrumentation-guide-configuration.module.css';
+
+export function InstrumentationGuideConfiguration(props: ConfigureStepProps) {
+  const { t } = useTranslation();
+  const [usingExistingToken, setUsingExistingToken] = 
useState(!props.canGenerateToken);
+  return (
+    <>
+      <div className={styles.guidePreparation}>
+        <SelectedDestination profiles={props.profiles} 
profileId={props.profileId} />
+        <TokenConfiguration
+          {...props}
+          usingExistingToken={usingExistingToken}
+          onUseExistingToken={() => setUsingExistingToken(true)}
+          onCancelExistingToken={() => setUsingExistingToken(false)}
+        />
+      </div>
+      <div className={configureStyles.configureActions}>
+        <Button 
onClick={props.onPrevious}>{t('instrumentation.action.previous')}</Button>
+        <Button type="primary" disabled={!props.canRender} 
loading={props.rendering} onClick={props.onRender}>
+          {t('instrumentation.action.next')}
+        </Button>
+      </div>
+      {props.renderError && <Alert type="error" showIcon 
message={t('instrumentation.v2.renderError')} />}
+    </>
+  );
+}
+
+function SelectedDestination(props: Pick<ConfigureStepProps, 'profiles' | 
'profileId'>) {
+  const { t } = useTranslation();
+  const profile = props.profiles.profiles.find(item => item.id === 
props.profileId);
+  if (!profile) return null;
+  return (
+    <div className={styles.selectedDestinationInline}>
+      <span>{t('instrumentation.v2.guided.selectedDestination')}</span>
+      <strong>{t(`instrumentation.v2.profileKind.${profile.kind}`)}</strong>
+      <small>{t(`instrumentation.v2.profileRoute.${profile.kind}`)}</small>
+      <span className={styles.selectedDestinationEndpoints}>
+        {intakeEndpointEntries(profile).map(([transport, endpoint]) => (
+          <span key={transport}>
+            {t(`instrumentation.v2.transport.${transport}`)}
+            <Typography.Text 
type="secondary">{t(`instrumentation.v2.security.${endpoint.security}`)}</Typography.Text>
+          </span>
+        ))}
+      </span>
+    </div>
+  );
+}
+
+function TokenConfiguration(
+  props: ConfigureStepProps & {
+    usingExistingToken: boolean;
+    onUseExistingToken: () => void;
+    onCancelExistingToken: () => void;
+  }
+) {
+  const { t } = useTranslation();
+  if (!props.requiresToken && !props.tokenAcknowledgementRequired) return null;
+  const profile = props.profiles.profiles.find(item => item.id === 
props.profileId);
+  return (
+    <div className={styles.tokenSetup}>
+      {props.requiresToken && profileUsesPlaintext(profile) && (
+        <Alert type="warning" showIcon 
message={t('instrumentation.token.plaintextBearerWarning')} />
+      )}
+      {props.requiresToken && <TokenSetupPrompt {...props} />}
+      {props.requiresToken && tokenFieldVisible(props) && (
+        <InstrumentationTokenField
+          token={props.token}
+          disabled={props.tokenAcknowledgementRequired}
+          onToken={props.onToken}
+        />
+      )}
+      {props.requiresToken && props.usingExistingToken && 
props.canGenerateToken && !props.token && (
+        <Button className={styles.cancelExistingToken!} type="link" 
onClick={props.onCancelExistingToken}>
+          {t('instrumentation.token.cancelExisting')}
+        </Button>
+      )}
+    </div>
+  );
+}
+
+function TokenSetupPrompt(
+  props: Pick<ConfigureStepProps, 'token' | 'canGenerateToken' | 
'onOpenToken'> & {
+    usingExistingToken: boolean;
+    onUseExistingToken: () => void;
+  }
+) {
+  const { t } = useTranslation();
+  const copyKey = tokenSetupCopyKey(props);
+  const showTokenActions = !props.token && !tokenFieldVisible(props);
+  return (
+    <div className={styles.tokenSetupPrompt}>
+      <span className={styles.tokenSetupCopy}>
+        <Typography.Text 
strong>{t(`instrumentation.token.${copyKey}Title`)}</Typography.Text>
+        <Typography.Text 
type="secondary">{t(`instrumentation.token.${copyKey}Description`)}</Typography.Text>
+      </span>
+      {showTokenActions && (
+        <span className={styles.tokenSetupActions}>
+          <Button type="primary" onClick={props.onOpenToken}>
+            {t('instrumentation.token.create')}
+          </Button>
+          <Button type="link" onClick={props.onUseExistingToken}>
+            {t('instrumentation.token.useExisting')}
+          </Button>
+        </span>
+      )}
+    </div>
+  );
+}
+
+function tokenSetupCopyKey(props: { token: string; usingExistingToken: 
boolean; canGenerateToken: boolean }) {
+  if (props.token) return 'ready';
+  if (props.usingExistingToken || !props.canGenerateToken) return 'existing';
+  return 'setup';
+}
+
+function tokenFieldVisible(props: { usingExistingToken: boolean; 
canGenerateToken: boolean }) {
+  return props.usingExistingToken || !props.canGenerateToken;
+}
+
+export function InstrumentationTokenModal(props: ConfigureStepProps) {
+  if (!props.canGenerateToken || !props.tokenDraft) return null;
+  return (
+    <InstrumentationAccessTokenModal
+      draft={props.tokenDraft}
+      tokenGenerating={props.tokenGenerating}
+      tokenError={props.tokenError}
+      onClose={props.onCloseToken}
+      onDraft={props.onTokenDraft}
+      onGenerate={props.onGenerateToken}
+    />
+  );
+}
diff --git 
a/web-app/src/features/instrumentation/components/instrumentation-guide-workspace.tsx
 
b/web-app/src/features/instrumentation/components/instrumentation-guide-workspace.tsx
index 736dff6ef8..3eb1b35c32 100644
--- 
a/web-app/src/features/instrumentation/components/instrumentation-guide-workspace.tsx
+++ 
b/web-app/src/features/instrumentation/components/instrumentation-guide-workspace.tsx
@@ -4,7 +4,7 @@
  * governing permissions and limitations under the License.
  */
 
-import { Button, Typography } from 'antd';
+import { Alert, Button, Typography } from 'antd';
 import { useTranslation } from 'react-i18next';
 
 import type { InstrumentationDraft } from '../model/instrumentation-flow';
@@ -26,6 +26,7 @@ export function InstrumentationGuideWorkspace(props: {
   draft: InstrumentationDraft;
   guide: RenderResponse;
   token: string;
+  tokenAcknowledgementRequired?: boolean;
   detection?: DetectionResponse;
   detecting: boolean;
   detectionError: boolean;
@@ -33,10 +34,17 @@ export function InstrumentationGuideWorkspace(props: {
   onEdit: () => void;
   onDetect: () => void;
   onOpen: (signal: Signal) => void;
+  onAcknowledgeToken?: () => void;
 }) {
   return (
     <div className={styles.workspace}>
-      <SelectionSummary catalog={props.catalog} draft={props.draft} 
onEdit={props.onEdit} />
+      {props.tokenAcknowledgementRequired && <TokenAcknowledgement 
onAcknowledge={props.onAcknowledgeToken} />}
+      <SelectionSummary
+        catalog={props.catalog}
+        draft={props.draft}
+        editingDisabled={Boolean(props.tokenAcknowledgementRequired)}
+        onEdit={props.onEdit}
+      />
       <div className={styles.workspaceBody}>
         <InstrumentationGuideBlocks guide={props.guide} token={props.token} 
onCopy={props.onCopy} />
         <DestinationRail {...props} />
@@ -45,7 +53,28 @@ export function InstrumentationGuideWorkspace(props: {
   );
 }
 
-function SelectionSummary(props: { catalog: CatalogResponse; draft: 
InstrumentationDraft; onEdit: () => void }) {
+function TokenAcknowledgement(props: { onAcknowledge: (() => void) | undefined 
}) {
+  const { t } = useTranslation();
+  return (
+    <Alert
+      type="warning"
+      showIcon
+      message={t('instrumentation.token.guideAcknowledgementRequired')}
+      action={
+        <Button size="small" onClick={() => props.onAcknowledge?.()}>
+          {t('instrumentation.token.acknowledge')}
+        </Button>
+      }
+    />
+  );
+}
+
+function SelectionSummary(props: {
+  catalog: CatalogResponse;
+  draft: InstrumentationDraft;
+  editingDisabled: boolean;
+  onEdit: () => void;
+}) {
   const { t } = useTranslation();
   const source = props.catalog.sources.find(item => item.id === 
props.draft.sourceId);
   const dimensions = ['framework', 'method', 'environment', 'platform'] as 
const;
@@ -53,7 +82,7 @@ function SelectionSummary(props: { catalog: CatalogResponse; 
draft: Instrumentat
     <aside className={styles.summary}>
       <div className={styles.railHeading}>
         <Typography.Text 
strong>{t('instrumentation.v2.selection')}</Typography.Text>
-        <Button size="small" type="link" onClick={props.onEdit}>
+        <Button size="small" type="link" disabled={props.editingDisabled} 
onClick={props.onEdit}>
           {t('common.edit')}
         </Button>
       </div>
diff --git 
a/web-app/src/features/instrumentation/components/instrumentation-guided-configuration.test.tsx
 
b/web-app/src/features/instrumentation/components/instrumentation-guided-configuration.test.tsx
new file mode 100644
index 0000000000..21ca72d2d1
--- /dev/null
+++ 
b/web-app/src/features/instrumentation/components/instrumentation-guided-configuration.test.tsx
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0.
+ */
+
+import { cleanup, fireEvent, render, screen } from '@testing-library/react';
+import { afterEach, describe, expect, it, vi } from 'vitest';
+
+vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => 
key }) }));
+
+import { InstrumentationConfigureStep } from 
'./instrumentation-configure-step';
+
+afterEach(cleanup);
+
+describe('guided instrumentation configuration', () => {
+  it('keeps service identity as one focused step before destination 
selection', () => {
+    const onNext = vi.fn();
+    renderConfigure({ phase: 'service', onNext });
+
+    expect(screen.getByRole('heading', { name: 
'instrumentation.v2.guided.serviceTitle' })).toBeVisible();
+    expect(screen.getByRole('textbox', { name: 
'instrumentation.field.serviceName' })).toBeVisible();
+    expect(screen.queryByRole('region', { name: 
'instrumentation.v2.destination' })).toBeNull();
+    expect(screen.queryByLabelText('instrumentation.field.token')).toBeNull();
+
+    fireEvent.click(screen.getByRole('button', { name: 
'instrumentation.action.next' }));
+    expect(onNext).toHaveBeenCalledOnce();
+  });
+
+  it('uses compact route rows and moves endpoint detail into the 
selected-route summary', () => {
+    const onProfile = vi.fn();
+    renderConfigure({ phase: 'destination', onProfile });
+
+    expect(screen.getByRole('heading', { name: 
'instrumentation.v2.guided.destinationTitle' })).toBeVisible();
+    expect(screen.getByRole('region', { name: 'instrumentation.v2.destination' 
})).toBeVisible();
+    expect(screen.getByRole('complementary', { name: 
'instrumentation.v2.guided.currentIntake' })).toBeVisible();
+    expect(screen.getAllByText('http://127.0.0.1:24318')).toHaveLength(1);
+    expect(screen.getAllByText('http://127.0.0.1:24317')).toHaveLength(1);
+    expect(screen.getAllByRole('button', { name: 
/instrumentation\.v2\.profileKind\./ })).toHaveLength(3);
+    
expect(screen.queryByText('instrumentation.v2.profileOwnership.hertzbeat_collector')).toBeNull();
+    
expect(screen.queryByText('instrumentation.v2.profileBoundary.hertzbeat_collector')).toBeNull();
+
+    fireEvent.click(screen.getByRole('button', { name: 
/instrumentation\.v2\.profileKind\.hertzbeat_collector/ }));
+    expect(onProfile).toHaveBeenCalledWith('collector:hybrid-local-0812');
+  });
+
+  it('guides token creation before continuing and keeps existing-token entry 
secondary', () => {
+    const onRender = vi.fn();
+    const onOpenToken = vi.fn();
+    const onToken = vi.fn();
+    const view = renderConfigure({ phase: 'guide', token: '', canRender: 
false, onOpenToken, onRender, onToken });
+
+    expect(screen.getByRole('heading', { name: 
'instrumentation.v2.guided.guideTitle' })).toBeVisible();
+    expect(screen.queryByLabelText('instrumentation.field.token')).toBeNull();
+    expect(screen.queryByRole('region', { name: 
'instrumentation.v2.destination' })).toBeNull();
+    expect(screen.getByRole('button', { name: 'instrumentation.token.create' 
})).toBeVisible();
+    expect(screen.getByRole('button', { name: 
'instrumentation.token.useExisting' })).toBeVisible();
+    expect(screen.getByRole('button', { name: 'instrumentation.action.next' 
})).toBeDisabled();
+
+    fireEvent.click(screen.getByRole('button', { name: 
'instrumentation.token.create' }));
+    expect(onOpenToken).toHaveBeenCalledOnce();
+
+    fireEvent.click(screen.getByRole('button', { name: 
'instrumentation.token.useExisting' }));
+    const tokenInput = screen.getByLabelText('instrumentation.field.token');
+    fireEvent.change(tokenInput, { target: { value: 'existing-token' } });
+    expect(onToken).toHaveBeenCalledWith('existing-token');
+
+    view.unmount();
+    renderConfigure({
+      phase: 'guide',
+      token: 'generated-token',
+      canRender: true,
+      tokenAcknowledgementRequired: true,
+      onRender
+    });
+    expect(screen.queryByLabelText('instrumentation.field.token')).toBeNull();
+    expect(screen.getByText('instrumentation.token.readyTitle')).toBeVisible();
+    expect(screen.queryByRole('button', { name: 'instrumentation.token.create' 
})).toBeNull();
+    expect(screen.queryByRole('button', { name: 
'instrumentation.token.useExisting' })).toBeNull();
+    fireEvent.click(screen.getByRole('button', { name: 
'instrumentation.action.next' }));
+    expect(onRender).toHaveBeenCalledOnce();
+  });
+});
+
+type ConfigureOverrides = Partial<React.ComponentProps<typeof 
InstrumentationConfigureStep>>;
+
+function renderConfigure(overrides: ConfigureOverrides) {
+  return render(createConfigureStep(overrides));
+}
+
+function createConfigureStep(overrides: ConfigureOverrides) {
+  const props: React.ComponentProps<typeof InstrumentationConfigureStep> = {
+    phase: 'service',
+    profiles: {
+      schemaVersion: 2,
+      status: 'available',
+      defaultProfileId: 'collector:hybrid-local-0812',
+      profiles: [
+        {
+          id: 'server',
+          kind: 'server',
+          availability: 'unavailable',
+          supportedTransports: [],
+          endpoints: {},
+          authorizationHeader: null,
+          errorCode: 'intake_profile_not_advertised'
+        },
+        {
+          id: 'collector:hybrid-local-0812',
+          kind: 'hertzbeat_collector',
+          availability: 'available',
+          supportedTransports: ['http_protobuf', 'grpc'],
+          endpoints: {
+            http_protobuf: { url: 'http://127.0.0.1:24318', security: 
'plaintext' },
+            grpc: { url: 'http://127.0.0.1:24317', security: 'plaintext' }
+          },
+          authentication: 'bearer_token',
+          authorizationHeader: 'Authorization',
+          collectorId: 'hybrid-local-0812'
+        },
+        {
+          id: 'collector:stale',
+          kind: 'hertzbeat_collector',
+          availability: 'unavailable',
+          supportedTransports: [],
+          endpoints: {},
+          authorizationHeader: null,
+          errorCode: 'intake_profile_not_advertised'
+        }
+      ]
+    },
+    profileId: 'collector:hybrid-local-0812',
+    service: { name: 'checkout', namespace: 'default', environment: 
'production' },
+    platformOptions: [],
+    canRender: true,
+    rendering: false,
+    renderError: false,
+    token: 'test-token-value',
+    tokenGenerating: false,
+    tokenError: false,
+    tokenAcknowledgementRequired: false,
+    requiresToken: true,
+    canGenerateToken: true,
+    onProfile: vi.fn(),
+    onService: vi.fn(),
+    onPlatform: vi.fn(),
+    onToken: vi.fn(),
+    onRender: vi.fn(),
+    onPrevious: vi.fn(),
+    onNext: vi.fn(),
+    onOpenToken: vi.fn(),
+    onCloseToken: vi.fn(),
+    onTokenDraft: vi.fn(),
+    onGenerateToken: vi.fn(),
+    onAcknowledgeToken: vi.fn(),
+    ...overrides
+  };
+  return <InstrumentationConfigureStep {...props} />;
+}
diff --git 
a/web-app/src/features/instrumentation/components/instrumentation-onboarding.module.css
 
b/web-app/src/features/instrumentation/components/instrumentation-onboarding.module.css
index 3d972059df..2611db0d08 100644
--- 
a/web-app/src/features/instrumentation/components/instrumentation-onboarding.module.css
+++ 
b/web-app/src/features/instrumentation/components/instrumentation-onboarding.module.css
@@ -2,7 +2,7 @@
   min-height: 100dvh;
   width: 100%;
   display: grid;
-  grid-template-rows: auto auto 1fr;
+  grid-template-rows: auto 1fr;
   background: var(--hb-bg-canvas);
 }
 
@@ -37,16 +37,23 @@
   justify-self: end;
 }
 
-.progressBand {
-  border-bottom: 1px solid var(--hb-border);
-  padding: 22px 32px;
+.onboardingBody {
+  width: min(100%, 1440px);
+  min-height: 0;
+  display: grid;
+  grid-template-columns: 190px minmax(0, 1fr);
+  margin: 0 auto;
+}
+
+.flowRail {
+  border-right: 1px solid var(--hb-border);
+  padding: 34px 22px;
   background: var(--hb-bg-chrome);
 }
 
 .onboardingContent {
-  width: min(100%, 1384px);
-  margin: 0 auto;
-  padding: 32px;
+  min-width: 0;
+  padding: 34px 40px 28px;
 }
 
 .sourceStage {
@@ -76,7 +83,17 @@
     justify-self: stretch;
   }
 
-  .progressBand,
+  .onboardingBody {
+    grid-template-columns: 1fr;
+  }
+
+  .flowRail {
+    overflow-x: auto;
+    border-right: 0;
+    border-bottom: 1px solid var(--hb-border);
+    padding: 16px;
+  }
+
   .onboardingContent {
     padding: 20px 16px;
   }
diff --git 
a/web-app/src/features/instrumentation/components/instrumentation-progress.tsx 
b/web-app/src/features/instrumentation/components/instrumentation-progress.tsx
index 11e13f04ab..deafa0edce 100644
--- 
a/web-app/src/features/instrumentation/components/instrumentation-progress.tsx
+++ 
b/web-app/src/features/instrumentation/components/instrumentation-progress.tsx
@@ -7,17 +7,28 @@
 import { Steps } from 'antd';
 import { useTranslation } from 'react-i18next';
 
-import { INSTRUMENTATION_STAGES, type InstrumentationStage } from 
'../model/instrumentation-flow';
+import {
+  INSTRUMENTATION_CONFIGURE_PHASES,
+  type InstrumentationConfigurePhase
+} from '../model/instrumentation-guided-flow';
+import type { InstrumentationStage } from '../model/instrumentation-flow';
 import styles from './instrumentation-shell.module.css';
 
-export function InstrumentationProgress(props: { stage: InstrumentationStage 
}) {
+const GUIDED_STEPS = ['source', ...INSTRUMENTATION_CONFIGURE_PHASES] as const;
+
+export function InstrumentationProgress(props: {
+  stage: InstrumentationStage;
+  configurePhase: InstrumentationConfigurePhase;
+}) {
   const { t } = useTranslation();
+  const current = props.stage === 'source' ? 0 : 
GUIDED_STEPS.indexOf(props.configurePhase);
   return (
-    <div className={styles.progress}>
+    <div className={styles.guidedProgress}>
       <Steps
         size="small"
-        current={INSTRUMENTATION_STAGES.indexOf(props.stage)}
-        items={INSTRUMENTATION_STAGES.map(stage => ({ title: 
t(`instrumentation.v2.stage.${stage}`) }))}
+        direction="vertical"
+        current={current}
+        items={GUIDED_STEPS.map(step => ({ title: 
t(`instrumentation.v2.guided.stage.${step}`) }))}
       />
     </div>
   );
diff --git 
a/web-app/src/features/instrumentation/components/instrumentation-shell.module.css
 
b/web-app/src/features/instrumentation/components/instrumentation-shell.module.css
index e8482c68a7..dd032cf1c9 100644
--- 
a/web-app/src/features/instrumentation/components/instrumentation-shell.module.css
+++ 
b/web-app/src/features/instrumentation/components/instrumentation-shell.module.css
@@ -1,10 +1,20 @@
-.progress {
-  max-width: 1320px;
-  margin: 0 auto;
-  display: grid;
-  grid-template-columns: minmax(0, 1fr) auto;
-  align-items: center;
-  gap: 16px;
+.guidedProgress {
+  position: sticky;
+  top: 24px;
+}
+
+.guidedProgress :global(.ant-steps-item-title) {
+  color: var(--hb-text-secondary) !important;
+  font-size: 13px;
+}
+
+.guidedProgress :global(.ant-steps-item-active .ant-steps-item-title) {
+  color: var(--hb-text-primary) !important;
+  font-weight: 600;
+}
+
+.guidedProgress :global(.ant-steps-item-description) {
+  display: none;
 }
 
 .section h4 {
@@ -43,12 +53,12 @@
 
 .sourceTile {
   min-width: max-content;
-  min-height: 44px;
+  min-height: 36px;
   display: flex;
   flex: 0 0 auto;
   align-items: center;
-  gap: 10px;
-  padding: 9px 14px;
+  gap: 8px;
+  padding: 6px 10px;
   border: var(--ant-line-width) solid var(--hb-border);
   border-radius: var(--hb-radius-control);
   background: var(--hb-bg-raised);
@@ -91,11 +101,11 @@
 }
 
 .sourceIcon {
-  width: var(--ant-font-size-lg);
-  height: var(--ant-font-size-lg);
+  width: var(--ant-font-size);
+  height: var(--ant-font-size);
   flex: 0 0 auto;
   object-fit: contain;
-  font-size: var(--ant-font-size-lg);
+  font-size: var(--ant-font-size);
   color: var(--hb-brand-accent);
 }
 
@@ -153,8 +163,9 @@
 
 .categoryActionSelected,
 .categoryActionSelected:hover {
+  border-radius: 0;
   color: var(--hb-text-primary) !important;
-  background: var(--hb-bg-selected) !important;
+  background: transparent !important;
   box-shadow: inset 2px 0 0 var(--hb-brand-accent);
 }
 
@@ -174,6 +185,22 @@
   color: var(--hb-status-degraded);
 }
 
+@media (max-width: 760px) {
+  .guidedProgress {
+    position: static;
+    min-width: 620px;
+  }
+
+  .guidedProgress :global(.ant-steps) {
+    display: flex;
+    flex-direction: row;
+  }
+
+  .guidedProgress :global(.ant-steps-item) {
+    flex: 1;
+  }
+}
+
 .sourceAssistiveText {
   position: absolute;
   width: 1px;
diff --git 
a/web-app/src/features/instrumentation/components/instrumentation-v2-flow.test.tsx
 
b/web-app/src/features/instrumentation/components/instrumentation-v2-flow.test.tsx
index 1301beeff5..8ad7868a1c 100644
--- 
a/web-app/src/features/instrumentation/components/instrumentation-v2-flow.test.tsx
+++ 
b/web-app/src/features/instrumentation/components/instrumentation-v2-flow.test.tsx
@@ -25,6 +25,7 @@ import { InstrumentationGuideBlocks } from 
'./instrumentation-guide-blocks';
 import { InstrumentationGuideWorkspace } from 
'./instrumentation-guide-workspace';
 import { InstrumentationSourceStep } from './instrumentation-source-step';
 import configureCss from './instrumentation-configure.module.css?raw';
+import destinationCss from 
'./instrumentation-destination-selector.module.css?raw';
 import guideCss from './instrumentation-guide.module.css?raw';
 import questionCss from './instrumentation-question.module.css?raw';
 import shellCss from './instrumentation-shell.module.css?raw';
@@ -53,8 +54,9 @@ describe('instrumentation v2 interaction', () => {
     expect(screen.getByRole('button', { name: 
/^instrumentation\.v2\.directory\.source\.logstash/ })).toBeVisible();
     
expect(shellCss).toMatch(/\.sourceGrid\s*\{[^}]*display:\s*flex[^}]*flex-wrap:\s*wrap/);
     expect(shellCss).toMatch(
-      
/\.sourceTile\s*\{[^}]*min-width:\s*max-content[^}]*min-height:\s*44px[^}]*flex:\s*0\s+0\s+auto/
+      
/\.sourceTile\s*\{[^}]*min-width:\s*max-content[^}]*min-height:\s*36px[^}]*flex:\s*0\s+0\s+auto/
     );
+    
expect(shellCss).toMatch(/\.sourceTile\s*\{[^}]*gap:\s*8px[^}]*padding:\s*6px 
10px/);
     expect(shellCss).toMatch(
       /\.sourceTile\s*\{[^}]*border:\s*var\(--ant-line-width\) solid 
var\(--hb-border\)[^}]*background:\s*var\(--hb-bg-raised\)/
     );
@@ -67,8 +69,12 @@ describe('instrumentation v2 interaction', () => {
     );
     
expect(shellCss).toMatch(/\.sourceName\s*\{[^}]*font-weight:\s*600[^}]*white-space:\s*nowrap/);
     expect(shellCss).toMatch(
-      
/\.sourceIcon\s*\{[^}]*width:\s*var\(--ant-font-size-lg\)[^}]*height:\s*var\(--ant-font-size-lg\)/
+      
/\.sourceIcon\s*\{[^}]*width:\s*var\(--ant-font-size\)[^}]*height:\s*var\(--ant-font-size\)/
     );
+    expect(shellCss).toMatch(
+      
/\.categoryActionSelected,[\s\S]*?\{[^}]*border-radius:\s*0[^}]*background:\s*transparent[^}]*box-shadow:\s*inset
 2px 0 0 var\(--hb-brand-accent\)/
+    );
+    
expect(shellCss).not.toMatch(/\.categoryActionSelected,[\s\S]*?\{[^}]*background:\s*var\(--hb-bg-selected\)/);
     const assistiveDescription = 
within(javaSource).getByText('instrumentation.v2.directory.source.java_description');
     expect(assistiveDescription).toHaveClass(/sourceAssistiveText/);
     
expect(within(assistiveDescription).getByText(/instrumentation\.signal\.metrics/)).toBeInTheDocument();
@@ -143,7 +149,25 @@ describe('instrumentation v2 interaction', () => {
   });
 
   it('explains all three telemetry routes and keeps missing destinations 
visible without inventing endpoints', () => {
+    const availableProfiles = {
+      schemaVersion: 2 as const,
+      status: 'available' as const,
+      defaultProfileId: 'server-default',
+      profiles: [
+        guide.intakeProfile,
+        {
+          id: 'hybrid-edge',
+          kind: 'hertzbeat_collector' as const,
+          availability: 'unavailable' as const,
+          supportedTransports: [],
+          endpoints: {},
+          authorizationHeader: null,
+          errorCode: 'intake_profile_unavailable'
+        }
+      ]
+    };
     const props = {
+      phase: 'destination' as const,
       profileId: '',
       service: {
         name: '',
@@ -168,52 +192,33 @@ describe('instrumentation v2 interaction', () => {
       onPlatform: vi.fn(),
       onToken: vi.fn(),
       onRender: vi.fn(),
+      onPrevious: vi.fn(),
+      onNext: vi.fn(),
       onOpenToken: vi.fn(),
       onCloseToken: vi.fn(),
       onTokenDraft: vi.fn(),
       onGenerateToken: vi.fn(),
       onAcknowledgeToken: vi.fn()
     };
-    const view = render(
-      <InstrumentationConfigureStep
-        profiles={{
-          schemaVersion: 2,
-          status: 'available',
-          defaultProfileId: 'server-default',
-          profiles: [
-            guide.intakeProfile,
-            {
-              id: 'hybrid-edge',
-              kind: 'hertzbeat_collector',
-              availability: 'unavailable',
-              supportedTransports: [],
-              endpoints: {},
-              authorizationHeader: null,
-              errorCode: 'intake_profile_unavailable'
-            }
-          ]
-        }}
-        {...props}
-      />
-    );
-    expect(screen.getByRole('heading', { name: 
'instrumentation.v2.configureTitle', level: 3 })).toBeVisible();
-    expect(screen.getByRole('region', { name: 
'instrumentation.v2.serviceContext' })).toBeVisible();
+    const view = render(<InstrumentationConfigureStep 
profiles={availableProfiles} {...props} />);
+    expect(screen.getByRole('heading', { name: 
'instrumentation.v2.guided.destinationTitle', level: 3 })).toBeVisible();
     expect(screen.getByRole('region', { name: 'instrumentation.v2.destination' 
})).toBeVisible();
-    
expect(screen.getByText('instrumentation.v2.destinationDescription')).toBeVisible();
+    
expect(screen.getByText('instrumentation.v2.guided.destinationDescription')).toBeVisible();
     
expect(screen.getByText('instrumentation.v2.profileRoute.server')).toBeVisible();
     
expect(screen.getByText('instrumentation.v2.profileRoute.hertzbeat_collector')).toBeVisible();
     
expect(screen.getByText('instrumentation.v2.profileRoute.external_otel_collector')).toBeVisible();
     
expect(screen.getByText('instrumentation.v2.profilePurpose.server')).toBeVisible();
     
expect(screen.getByText('instrumentation.v2.profilePurpose.hertzbeat_collector')).toBeVisible();
     
expect(screen.getByText('instrumentation.v2.profilePurpose.external_otel_collector')).toBeVisible();
-    
expect(screen.getByText('instrumentation.v2.profileBoundary.server')).toBeVisible();
-    
expect(screen.getByText('instrumentation.v2.profileBoundary.hertzbeat_collector')).toBeVisible();
-    
expect(screen.getByText('instrumentation.v2.profileBoundary.external_otel_collector')).toBeVisible();
-    expect(configureCss).toMatch(/\.destinationList\s*\{[^}]*display:\s*grid/);
-    expect(configureCss).toMatch(
+    
expect(screen.queryByText('instrumentation.v2.profileBoundary.server')).toBeNull();
+    
expect(screen.queryByText('instrumentation.v2.profileBoundary.hertzbeat_collector')).toBeNull();
+    
expect(screen.queryByText('instrumentation.v2.profileBoundary.external_otel_collector')).toBeNull();
+    
expect(destinationCss).toMatch(/\.destinationList\s*\{[^}]*display:\s*grid/);
+    expect(destinationCss).toMatch(
       
/\.destinationAvailability\s*\{[^}]*grid-column:\s*2[^}]*grid-row:\s*1[^}]*justify-self:\s*end/
     );
     
expect(configureCss).toMatch(/\.configureActions\s*\{[^}]*border-top:\s*1px 
solid var\(--hb-border\)/);
+    view.rerender(<InstrumentationConfigureStep profiles={availableProfiles} 
{...props} phase="service" />);
     const serviceName = screen.getByRole('textbox', { name: 
'instrumentation.field.serviceName' });
     const serviceEnvironment = screen.getByRole('textbox', {
       name: 'instrumentation.field.serviceEnvironment'
@@ -239,6 +244,7 @@ describe('instrumentation v2 interaction', () => {
     expect(props.onService).toHaveBeenNthCalledWith(4, { serviceInstanceId: 
'checkout-7d9' });
     expect(props.onService).toHaveBeenNthCalledWith(5, { endpoint: '/checkout' 
});
     expect(screen.queryByText(/entity/iu)).toBeNull();
+    view.rerender(<InstrumentationConfigureStep profiles={availableProfiles} 
{...props} />);
     expect(screen.getByRole('button', { name: 
/instrumentation\.v2\.profileKind\.server/ })).toBeEnabled();
     expect(
       screen.getByRole('button', { name: 
/instrumentation\.v2\.profileKind\.hertzbeat_collector/ })
@@ -279,6 +285,7 @@ describe('instrumentation v2 interaction', () => {
     const onGenerateToken = vi.fn();
     render(
       <InstrumentationConfigureStep
+        phase="guide"
         profiles={{
           schemaVersion: 2,
           status: 'available',
@@ -303,6 +310,8 @@ describe('instrumentation v2 interaction', () => {
         onPlatform={vi.fn()}
         onToken={vi.fn()}
         onRender={vi.fn()}
+        onPrevious={vi.fn()}
+        onNext={vi.fn()}
         onOpenToken={onOpenToken}
         onCloseToken={vi.fn()}
         onTokenDraft={vi.fn()}
@@ -325,6 +334,7 @@ describe('instrumentation v2 interaction', () => {
     const onToken = vi.fn();
     render(
       <InstrumentationConfigureStep
+        phase="guide"
         profiles={{
           schemaVersion: 2,
           status: 'available',
@@ -348,6 +358,8 @@ describe('instrumentation v2 interaction', () => {
         onPlatform={vi.fn()}
         onToken={onToken}
         onRender={vi.fn()}
+        onPrevious={vi.fn()}
+        onNext={vi.fn()}
         onOpenToken={vi.fn()}
         onCloseToken={vi.fn()}
         onTokenDraft={vi.fn()}
@@ -365,6 +377,7 @@ describe('instrumentation v2 interaction', () => {
   it('shows endpoint security and an explicit Bearer risk for a selected 
plaintext destination', () => {
     render(
       <InstrumentationConfigureStep
+        phase="guide"
         profiles={{
           schemaVersion: 2,
           status: 'available',
@@ -395,6 +408,8 @@ describe('instrumentation v2 interaction', () => {
         onPlatform={vi.fn()}
         onToken={vi.fn()}
         onRender={vi.fn()}
+        onPrevious={vi.fn()}
+        onNext={vi.fn()}
         onOpenToken={vi.fn()}
         onCloseToken={vi.fn()}
         onTokenDraft={vi.fn()}
@@ -410,6 +425,7 @@ describe('instrumentation v2 interaction', () => {
   it('shows an unauthenticated destination without token controls, status, or 
Bearer warnings', () => {
     render(
       <InstrumentationConfigureStep
+        phase="guide"
         profiles={{
           schemaVersion: 2,
           status: 'available',
@@ -433,6 +449,8 @@ describe('instrumentation v2 interaction', () => {
         onPlatform={vi.fn()}
         onToken={vi.fn()}
         onRender={vi.fn()}
+        onPrevious={vi.fn()}
+        onNext={vi.fn()}
         onOpenToken={vi.fn()}
         onCloseToken={vi.fn()}
         onTokenDraft={vi.fn()}
@@ -445,10 +463,11 @@ describe('instrumentation v2 interaction', () => {
     
expect(screen.queryByText('instrumentation.token.notGenerated')).not.toBeInTheDocument();
     
expect(screen.queryByText('instrumentation.token.plaintextBearerWarning')).not.toBeInTheDocument();
     expect(screen.queryByRole('button', { name: 
'instrumentation.token.generateAccess' })).not.toBeInTheDocument();
-    expect(screen.getByRole('button', { name: 'instrumentation.action.render' 
})).toBeEnabled();
+    expect(screen.getByRole('button', { name: 'instrumentation.action.next' 
})).toBeEnabled();
   });
 
   it('materializes the memory-only token visibly without mutating the backend 
guide', () => {
+    const onAcknowledgeToken = vi.fn();
     render(
       <InstrumentationGuideWorkspace
         catalog={catalog}
@@ -461,6 +480,7 @@ describe('instrumentation v2 interaction', () => {
         }}
         guide={guide}
         token="valid-token-123"
+        tokenAcknowledgementRequired
         detection={detection}
         detecting={false}
         detectionError={false}
@@ -468,8 +488,13 @@ describe('instrumentation v2 interaction', () => {
         onEdit={vi.fn()}
         onDetect={vi.fn()}
         onOpen={vi.fn()}
+        onAcknowledgeToken={onAcknowledgeToken}
       />
     );
+    
expect(screen.getByText('instrumentation.token.guideAcknowledgementRequired')).toBeVisible();
+    expect(screen.getByRole('button', { name: 'common.edit' })).toBeDisabled();
+    fireEvent.click(screen.getByRole('button', { name: 
'instrumentation.token.acknowledge' }));
+    expect(onAcknowledgeToken).toHaveBeenCalledOnce();
     expect(screen.getByText('token=valid-token-123')).toBeVisible();
     expect(screen.getByText('https://example.test/otlp')).toBeVisible();
     expect(screen.getByText('instrumentation.v2.security.tls')).toBeVisible();
diff --git a/web-app/src/features/instrumentation/instrumentation-i18n.test.ts 
b/web-app/src/features/instrumentation/instrumentation-i18n.test.ts
index b289164475..209d09152b 100644
--- a/web-app/src/features/instrumentation/instrumentation-i18n.test.ts
+++ b/web-app/src/features/instrumentation/instrumentation-i18n.test.ts
@@ -129,6 +129,15 @@ describe('instrumentation v2 locale contract', () => {
       expect(loadError, localeName).not.toContain('instrumentation.v2');
     });
   });
+
+  it('uses short, task-oriented labels for the guided intake steps', () => {
+    expect(en.instrumentation.v2.guided.stage).toEqual({
+      source: 'Source',
+      service: 'Service details',
+      destination: 'Intake path',
+      guide: 'Setup guide'
+    });
+  });
 });
 
 function flatten(value: Record<string, unknown>, prefix = ''): string[] {
diff --git a/web-app/src/features/instrumentation/model/instrumentation-flow.ts 
b/web-app/src/features/instrumentation/model/instrumentation-flow.ts
index d47fb0cec2..5ee96d6b56 100644
--- a/web-app/src/features/instrumentation/model/instrumentation-flow.ts
+++ b/web-app/src/features/instrumentation/model/instrumentation-flow.ts
@@ -28,7 +28,7 @@ export type InstrumentationDraft = Omit<Selection, 
'sourceKind'> & {
 };
 export type ApplicationQuestion = 'framework' | 'method' | 'environment' | 
'platform';
 export type InstrumentationStage = 'source' | 'configure';
-export const INSTRUMENTATION_STAGES: InstrumentationStage[] = ['source', 
'configure'];
+const INSTRUMENTATION_STAGES: InstrumentationStage[] = ['source', 'configure'];
 
 export function previousInstrumentationStage(stage: InstrumentationStage): 
InstrumentationStage {
   const index = INSTRUMENTATION_STAGES.indexOf(stage);
diff --git 
a/web-app/src/features/instrumentation/model/instrumentation-guided-flow.ts 
b/web-app/src/features/instrumentation/model/instrumentation-guided-flow.ts
new file mode 100644
index 0000000000..ea448fcf37
--- /dev/null
+++ b/web-app/src/features/instrumentation/model/instrumentation-guided-flow.ts
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0.
+ */
+
+import type { ServiceIdentity } from './instrumentation-v2-contract';
+
+export const INSTRUMENTATION_CONFIGURE_PHASES = ['service', 'destination', 
'guide'] as const;
+export type InstrumentationConfigurePhase = (typeof 
INSTRUMENTATION_CONFIGURE_PHASES)[number];
+
+export function serviceConfigurationReady(
+  service: ServiceIdentity,
+  platform: string | undefined,
+  platformOptions: string[]
+) {
+  return Boolean(
+    service.name.trim() &&
+    service.namespace.trim() &&
+    service.environment.trim() &&
+    (platformOptions.length <= 1 || platform)
+  );
+}
diff --git 
a/web-app/src/features/instrumentation/pages/instrumentation-page.test.tsx 
b/web-app/src/features/instrumentation/pages/instrumentation-page.test.tsx
index ecf6f3a712..b7b7291709 100644
--- a/web-app/src/features/instrumentation/pages/instrumentation-page.test.tsx
+++ b/web-app/src/features/instrumentation/pages/instrumentation-page.test.tsx
@@ -53,7 +53,7 @@ afterEach(() => {
 });
 
 describe('InstrumentationPage immersive onboarding shell', () => {
-  it('owns an accessible HertzBeat header, exit action, and two-step progress 
without a context stage', () => {
+  it('owns an accessible HertzBeat header, exit action, and four focused 
workflow steps', () => {
     render(
       <MemoryRouter initialEntries={['/observability/integration']}>
         <Routes>
@@ -65,8 +65,7 @@ describe('InstrumentationPage immersive onboarding shell', () 
=> {
 
     expect(screen.getByRole('img', { name: 'HertzBeat' 
})).toHaveAttribute('src', '/assets/logo.svg');
     expect(screen.getByText('HertzBeat')).toBeVisible();
-    
expect(screen.getAllByText(/^instrumentation\.v2\.stage\./)).toHaveLength(2);
-    expect(screen.queryByText('instrumentation.v2.stage.context')).toBeNull();
+    
expect(screen.getAllByText(/^instrumentation\.v2\.guided\.stage\./)).toHaveLength(4);
     
expect(styles).toMatch(/\.onboardingHeader\s*\{[^}]*position:\s*(?!fixed)/);
 
     fireEvent.click(screen.getByRole('button', { name: 
'instrumentation.action.exit' }));
diff --git 
a/web-app/src/features/instrumentation/pages/instrumentation-page.tsx 
b/web-app/src/features/instrumentation/pages/instrumentation-page.tsx
index e9025377ef..ebec5edab3 100644
--- a/web-app/src/features/instrumentation/pages/instrumentation-page.tsx
+++ b/web-app/src/features/instrumentation/pages/instrumentation-page.tsx
@@ -6,22 +6,36 @@
 
 import { ArrowLeftOutlined } from '@ant-design/icons';
 import { Button } from 'antd';
+import { useState } from 'react';
 import { useTranslation } from 'react-i18next';
 import { useNavigate } from 'react-router-dom';
 
 import { applicationRoutePaths } from '@/shared/navigation/app-paths';
-import { InstrumentationConfigureStep } from 
'../components/instrumentation-configure-step';
-import { InstrumentationGuideWorkspace } from 
'../components/instrumentation-guide-workspace';
 import { InstrumentationInitializationEvidence } from 
'../components/instrumentation-initialization-evidence';
 import { InstrumentationProgress } from 
'../components/instrumentation-progress';
-import { InstrumentationSourceStep } from 
'../components/instrumentation-source-step';
 import styles from '../components/instrumentation-onboarding.module.css';
 import { useInstrumentationPageController } from 
'../controller/use-instrumentation-page-controller';
+import type { InstrumentationConfigurePhase } from 
'../model/instrumentation-guided-flow';
+import { InstrumentationStageContent } from './instrumentation-stage-content';
 
 export function InstrumentationPage() {
   const { t } = useTranslation();
   const navigate = useNavigate();
   const page = useInstrumentationPageController();
+  const [configurePhase, setConfigurePhase] = 
useState<InstrumentationConfigurePhase>('service');
+  const hasLocalBack = page.stage === 'configure' && configurePhase !== 
'service';
+  const hasBack = page.hasFlowBack || hasLocalBack;
+  const goBack = () => {
+    if (page.stage !== 'configure' || configurePhase === 'service') {
+      page.goBack();
+      return;
+    }
+    setConfigurePhase(configurePhase === 'guide' ? 'destination' : 'service');
+  };
+  const reset = () => {
+    setConfigurePhase('service');
+    page.reset();
+  };
   return (
     <div className={styles.page}>
       <header className={styles.onboardingHeader}>
@@ -30,116 +44,36 @@ export function InstrumentationPage() {
           type="text"
           icon={<ArrowLeftOutlined aria-hidden="true" />}
           disabled={page.tokenAcknowledgementRequired}
-          onClick={() => (page.hasFlowBack ? page.goBack() : void 
navigate(applicationRoutePaths.dashboard))}
+          onClick={() => (hasBack ? goBack() : void 
navigate(applicationRoutePaths.dashboard))}
         >
-          {t(page.hasFlowBack ? 'common.back' : 'instrumentation.action.exit')}
+          {t(hasBack ? 'common.back' : 'instrumentation.action.exit')}
         </Button>
         <div className={styles.brand}>
           <img src="/assets/logo.svg" alt="HertzBeat" width={24} height={23} />
           <strong>HertzBeat</strong>
         </div>
-        <Button className={styles.startOver!} 
disabled={page.tokenAcknowledgementRequired} onClick={page.reset}>
+        <Button className={styles.startOver!} 
disabled={page.tokenAcknowledgementRequired} onClick={reset}>
           {t('instrumentation.v2.startOver')}
         </Button>
       </header>
-      <div className={styles.progressBand}>
-        <InstrumentationProgress stage={page.stage} />
+      <div className={styles.onboardingBody}>
+        <aside className={styles.flowRail} 
aria-label={t('instrumentation.progress')}>
+          <InstrumentationProgress stage={page.stage} 
configurePhase={configurePhase} />
+        </aside>
+        <main className={styles.onboardingContent}>
+          <InstrumentationInitializationEvidence
+            catalogState={page.catalogState}
+            profilesState={page.profilesState}
+            retrying={page.initializationRetrying}
+            onRetry={() => void page.retryInitialization()}
+          />
+          <InstrumentationStageContent
+            page={page}
+            configurePhase={configurePhase}
+            onConfigurePhase={setConfigurePhase}
+          />
+        </main>
       </div>
-      <main className={styles.onboardingContent}>
-        <InstrumentationInitializationEvidence
-          catalogState={page.catalogState}
-          profilesState={page.profilesState}
-          retrying={page.initializationRetrying}
-          onRetry={() => void page.retryInitialization()}
-        />
-        <InstrumentationStageContent page={page} />
-      </main>
     </div>
   );
 }
-
-type PageController = ReturnType<typeof useInstrumentationPageController>;
-
-function InstrumentationStageContent({ page }: { page: PageController }) {
-  if (page.stage === 'source') return <SourceStage page={page} />;
-  return <ConfigureStage page={page} />;
-}
-
-function SourceStage({ page }: { page: PageController }) {
-  const { t } = useTranslation();
-  if (!page.catalog) return null;
-  return (
-    <div className={styles.sourceStage}>
-      <InstrumentationSourceStep
-        key={page.sourceDirectoryRevision}
-        catalog={page.catalog}
-        {...(page.draft.sourceId ? { sourceId: page.draft.sourceId } : {})}
-        {...(page.draft.recipeId ? { recipeId: page.draft.recipeId } : {})}
-        {...(page.draft.framework ? { framework: page.draft.framework } : {})}
-        {...(page.draft.method ? { method: page.draft.method } : {})}
-        {...(page.draft.environment ? { environment: page.draft.environment } 
: {})}
-        {...(page.draft.platform ? { platform: page.draft.platform } : {})}
-        onSource={page.chooseSource}
-        onApplicationAnswer={page.answerApplication}
-      />
-      <Button
-        className={styles.sourceContinue!}
-        type="primary"
-        disabled={!page.canContinueSource}
-        onClick={() => page.setStage('configure')}
-      >
-        {t('instrumentation.action.continue')}
-      </Button>
-    </div>
-  );
-}
-
-function ConfigureStage({ page }: { page: PageController }) {
-  if (!page.profiles || !page.catalog) return null;
-  return (
-    <>
-      <InstrumentationConfigureStep
-        profiles={page.profiles}
-        profileId={page.draft.intakeProfileId}
-        service={page.draft.service}
-        platform={page.draft.platform}
-        platformOptions={page.platformOptions}
-        canRender={page.canRender}
-        rendering={page.rendering}
-        renderError={page.renderError}
-        token={page.token}
-        tokenDraft={page.tokenDraft}
-        tokenGenerating={page.tokenGenerating}
-        tokenError={page.tokenError}
-        tokenAcknowledgementRequired={page.tokenAcknowledgementRequired}
-        requiresToken={page.requiresToken}
-        canGenerateToken={page.canGenerateToken}
-        onProfile={intakeProfileId => page.patchDraft({ intakeProfileId })}
-        onService={page.patchService}
-        onPlatform={platform => page.patchDraft({ platform })}
-        onToken={page.setToken}
-        onRender={() => void page.renderGuide()}
-        onOpenToken={page.openTokenGenerator}
-        onCloseToken={page.closeTokenGenerator}
-        onTokenDraft={page.updateTokenDraft}
-        onGenerateToken={() => void page.generateToken()}
-        onAcknowledgeToken={page.acknowledgeGeneratedToken}
-      />
-      {page.guide && (
-        <InstrumentationGuideWorkspace
-          catalog={page.catalog}
-          draft={page.draft}
-          guide={page.guide}
-          token={page.token}
-          onCopy={page.copyBlock}
-          detecting={page.detecting}
-          detectionError={page.detectionError}
-          {...(page.detection ? { detection: page.detection } : {})}
-          onEdit={page.goBack}
-          onDetect={() => void page.detect()}
-          onOpen={page.openQuery}
-        />
-      )}
-    </>
-  );
-}
diff --git 
a/web-app/src/features/instrumentation/pages/instrumentation-stage-content.tsx 
b/web-app/src/features/instrumentation/pages/instrumentation-stage-content.tsx
new file mode 100644
index 0000000000..2e13bc9833
--- /dev/null
+++ 
b/web-app/src/features/instrumentation/pages/instrumentation-stage-content.tsx
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0.
+ */
+
+import { Button } from 'antd';
+import { useTranslation } from 'react-i18next';
+
+import { InstrumentationConfigureStep } from 
'../components/instrumentation-configure-step';
+import { InstrumentationGuideWorkspace } from 
'../components/instrumentation-guide-workspace';
+import { InstrumentationSourceStep } from 
'../components/instrumentation-source-step';
+import styles from '../components/instrumentation-onboarding.module.css';
+import type { useInstrumentationPageController } from 
'../controller/use-instrumentation-page-controller';
+import type { InstrumentationConfigurePhase } from 
'../model/instrumentation-guided-flow';
+
+type PageController = ReturnType<typeof useInstrumentationPageController>;
+type StageProps = {
+  page: PageController;
+  configurePhase: InstrumentationConfigurePhase;
+  onConfigurePhase: (phase: InstrumentationConfigurePhase) => void;
+};
+
+export function InstrumentationStageContent(props: StageProps) {
+  if (props.page.stage === 'source') return <SourceStage page={props.page} 
onContinue={props.onConfigurePhase} />;
+  if (!props.page.profiles || !props.page.catalog) return null;
+  if (props.configurePhase === 'guide' && props.page.guide) return <GuideStage 
{...props} />;
+  return <ConfigureFormStage {...props} />;
+}
+
+function SourceStage(props: { page: PageController; onContinue: (phase: 
InstrumentationConfigurePhase) => void }) {
+  const { t } = useTranslation();
+  const { page } = props;
+  if (!page.catalog) return null;
+  return (
+    <div className={styles.sourceStage}>
+      <InstrumentationSourceStep
+        key={page.sourceDirectoryRevision}
+        catalog={page.catalog}
+        {...sourceSelections(page)}
+        onSource={page.chooseSource}
+        onApplicationAnswer={page.answerApplication}
+      />
+      <Button
+        className={styles.sourceContinue!}
+        type="primary"
+        disabled={!page.canContinueSource}
+        onClick={() => {
+          props.onContinue('service');
+          page.setStage('configure');
+        }}
+      >
+        {t('instrumentation.action.continue')}
+      </Button>
+    </div>
+  );
+}
+
+function sourceSelections(page: PageController) {
+  return {
+    ...(page.draft.sourceId ? { sourceId: page.draft.sourceId } : {}),
+    ...(page.draft.recipeId ? { recipeId: page.draft.recipeId } : {}),
+    ...(page.draft.framework ? { framework: page.draft.framework } : {}),
+    ...(page.draft.method ? { method: page.draft.method } : {}),
+    ...(page.draft.environment ? { environment: page.draft.environment } : {}),
+    ...(page.draft.platform ? { platform: page.draft.platform } : {})
+  };
+}
+
+function GuideStage({ page, onConfigurePhase }: StageProps) {
+  if (!page.catalog || !page.guide) return null;
+  return (
+    <InstrumentationGuideWorkspace
+      catalog={page.catalog}
+      draft={page.draft}
+      guide={page.guide}
+      token={page.token}
+      tokenAcknowledgementRequired={page.tokenAcknowledgementRequired}
+      onCopy={page.copyBlock}
+      detecting={page.detecting}
+      detectionError={page.detectionError}
+      {...(page.detection ? { detection: page.detection } : {})}
+      onEdit={() => onConfigurePhase('service')}
+      onDetect={() => void page.detect()}
+      onOpen={page.openQuery}
+      onAcknowledgeToken={page.acknowledgeGeneratedToken}
+    />
+  );
+}
+
+function ConfigureFormStage({ page, configurePhase, onConfigurePhase }: 
StageProps) {
+  if (!page.profiles) return null;
+  const previous = () => {
+    if (configurePhase === 'service') page.goBack();
+    else onConfigurePhase(configurePhase === 'guide' ? 'destination' : 
'service');
+  };
+  return (
+    <InstrumentationConfigureStep
+      phase={configurePhase}
+      profiles={page.profiles}
+      profileId={page.draft.intakeProfileId}
+      service={page.draft.service}
+      platform={page.draft.platform}
+      platformOptions={page.platformOptions}
+      canRender={page.canRender}
+      rendering={page.rendering}
+      renderError={page.renderError}
+      token={page.token}
+      tokenDraft={page.tokenDraft}
+      tokenGenerating={page.tokenGenerating}
+      tokenError={page.tokenError}
+      tokenAcknowledgementRequired={page.tokenAcknowledgementRequired}
+      requiresToken={page.requiresToken}
+      canGenerateToken={page.canGenerateToken}
+      onProfile={intakeProfileId => page.patchDraft({ intakeProfileId })}
+      onService={page.patchService}
+      onPlatform={platform => page.patchDraft({ platform })}
+      onToken={page.setToken}
+      onRender={() => void page.renderGuide()}
+      onPrevious={previous}
+      onNext={() => onConfigurePhase(configurePhase === 'service' ? 
'destination' : 'guide')}
+      onOpenToken={page.openTokenGenerator}
+      onCloseToken={page.closeTokenGenerator}
+      onTokenDraft={page.updateTokenDraft}
+      onGenerateToken={() => void page.generateToken()}
+      onAcknowledgeToken={page.acknowledgeGeneratedToken}
+    />
+  );
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to