diff --git a/public/tour-pipelines/Hello World.pipeline.component.yaml b/public/tour-pipelines/Hello World.pipeline.component.yaml
new file mode 100644
index 000000000..6649a0690
--- /dev/null
+++ b/public/tour-pipelines/Hello World.pipeline.component.yaml
@@ -0,0 +1,134 @@
+name: Hello World
+description: A simple pipeline that demonstrates the hello world component.
+inputs:
+ - name: name
+ type: String
+ description: The name to greet.
+ default: ''
+ annotations:
+ editor.position: '{"x":140,"y":0}'
+ value: ''
+ optional: false
+outputs:
+ - name: greeting
+ type: Text
+ description: The generated greeting message.
+ annotations:
+ editor.position: '{"x": 850, "y": 0}'
+implementation:
+ graph:
+ tasks:
+ Greet:
+ componentRef:
+ name: Hello world
+ digest: 8cf1bcc31ce9cd9be6982a7ba95cf233f23efcb7769842b6b03e917ad5dfdd0a
+ spec:
+ name: Hello world
+ description: A simple hello world component that generates a greeting.
+ metadata:
+ annotations:
+ cloud_pipelines.net: 'true'
+ component_yaml_path: hello_world.component.yaml
+ python_original_code: |
+ from cloud_pipelines import components
+
+
+ def hello_world(
+ name: str,
+ greeting_output: components.OutputPath("Text"),
+ greeting_prefix: str = "Hello",
+ ):
+ """A simple hello world component that generates a greeting.
+
+ Args:
+ name: The name to greet.
+ greeting_output: Output file containing the greeting message.
+ greeting_prefix: Prefix for the greeting (default: Hello).
+ """
+ greeting = f"{greeting_prefix}, {name}! Welcome to Tangle."
+ print(greeting)
+ with open(greeting_output, "w") as f:
+ f.write(greeting)
+ components new regenerate python-function-component: 'true'
+ inputs:
+ - name: name
+ type: String
+ description: The name to greet.
+ - name: greeting_prefix
+ type: String
+ description: 'Prefix for the greeting (default: Hello).'
+ default: Hello
+ optional: true
+ outputs:
+ - name: greeting_output
+ type: Text
+ description: Output file containing the greeting message.
+ implementation:
+ container:
+ image: python:3.12
+ command:
+ - sh
+ - '-ec'
+ - |
+ program_path=$(mktemp)
+ printf "%s" "$0" > "$program_path"
+ python3 -u "$program_path" "$@"
+ - |
+ def _make_parent_dirs_and_return_path(file_path: str):
+ import os
+
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
+ return file_path
+
+ def hello_world(
+ name,
+ greeting_output,
+ greeting_prefix = "Hello",
+ ):
+ """A simple hello world component that generates a greeting.
+
+ Args:
+ name: The name to greet.
+ greeting_output: Output file containing the greeting message.
+ greeting_prefix: Prefix for the greeting (default: Hello).
+ """
+ greeting = f"{greeting_prefix}, {name}! Welcome to Tangle."
+ print(greeting)
+ with open(greeting_output, "w") as f:
+ f.write(greeting)
+
+ import argparse
+ _parser = argparse.ArgumentParser(prog='Hello world', description='A simple hello world component that generates a greeting.')
+ _parser.add_argument("--name", dest="name", type=str, required=True, default=argparse.SUPPRESS)
+ _parser.add_argument("--greeting-prefix", dest="greeting_prefix", type=str, required=False, default=argparse.SUPPRESS)
+ _parser.add_argument("--greeting-output", dest="greeting_output", type=_make_parent_dirs_and_return_path, required=True, default=argparse.SUPPRESS)
+ _parsed_args = vars(_parser.parse_args())
+
+ _outputs = hello_world(**_parsed_args)
+ args:
+ - '--name'
+ - inputValue: name
+ - if:
+ cond:
+ isPresent: greeting_prefix
+ then:
+ - '--greeting-prefix'
+ - inputValue: greeting_prefix
+ - '--greeting-output'
+ - outputPath: greeting_output
+ arguments:
+ name:
+ graphInput:
+ inputName: name
+ annotations:
+ editor.position: '{"x":430,"y":0}'
+ editor.collapsed: 'true'
+ outputValues:
+ greeting:
+ taskOutput:
+ outputName: greeting_output
+ taskId: Greet
+metadata:
+ annotations:
+ editor.flow-direction: left-to-right
+ notes: Enter a name in the Input Node or configure it when submitting the pipeline.
diff --git a/src/components/Learn/tours/navigatingEditor.tour.json b/src/components/Learn/tours/navigatingEditor.tour.json
new file mode 100644
index 000000000..d697e759c
--- /dev/null
+++ b/src/components/Learn/tours/navigatingEditor.tour.json
@@ -0,0 +1,113 @@
+{
+ "id": "navigating-the-editor",
+ "displayName": "Guided Tour: Navigating the Editor",
+ "requiresEditor": true,
+ "starterPipelineUrl": "tour-pipelines/Hello World.pipeline.component.yaml",
+ "steps": [
+ {
+ "selector": "[data-tour-anchor=\"no-spotlight\"]",
+ "content": "Welcome to the pipeline editor! This quick tour will show you around so you know where to find everything: the menu bar, canvas, dockable panels, and floating windows.",
+ "position": "center"
+ },
+ {
+ "selector": "[data-tour=\"editor-top-bar-left\"]",
+ "highlightedSelectors": [
+ "[data-tour=\"editor-top-bar-left\"]",
+ "[data-tour=\"editor-menu-items\"]"
+ ],
+ "content": "The top bar holds your pipeline name and editor menus.\n\nEach menu groups one type of command: **File** for pipeline operations, **View** for layout presets, **Runs** for submissions, **Components** for your libraries, and **Windows** for panels. A **Node** menu also appears here when you have a task selected.",
+ "position": "bottom"
+ },
+ {
+ "selector": "[data-tour=\"editor-top-bar-actions\"]",
+ "content": "Over on the right are your quick actions. Submit a run, check autosave status, jump to Settings, or open the documentation.",
+ "position": "bottom"
+ },
+ {
+ "selector": "[data-tour=\"editor-canvas\"]",
+ "content": "This is your workspace. Drag components onto it, connect tasks by linking their input and output handles, and pan or zoom around larger graphs.\n\nUseful controls sit along the bottom: a **minimap** in the bottom-left, and **viewport controls** with **undo/redo** in the bottom-right.",
+ "position": "center",
+ "resizeObservables": ["[data-tour=\"editor-canvas\"]"]
+ },
+ {
+ "selector": "[data-dock-area=\"left\"]",
+ "highlightedSelectors": [
+ "[data-dock-window=\"component-library\"]",
+ "[data-dock-window-content=\"component-library\"]",
+ "[data-dock-window=\"runs-and-submission\"]",
+ "[data-dock-window-content=\"runs-and-submission\"]",
+ "[data-dock-window=\"recent-runs\"]",
+ "[data-dock-window-content=\"recent-runs\"]"
+ ],
+ "content": "The left sidebar holds your docked panels.\n\n**Components** lets you browse and drag tasks onto the canvas. **Runs and submission** lets you submit your pipeline, and **Recent runs** shows the latest runs of this pipeline.",
+ "position": "right",
+ "resizeObservables": ["[data-dock-area=\"left\"]"]
+ },
+ {
+ "selector": "[data-dock-area=\"right\"]",
+ "content": "The right sidebar holds Pipeline Details and its properties. Set the pipeline description, tags, and notes here, and review any validation warnings.",
+ "resizeObservables": ["[data-dock-area=\"right\"]"]
+ },
+ {
+ "selector": "[data-tour-node=\"task\"][data-task-name=\"Greet\"]",
+ "content": "Try clicking the **Greet** task on the canvas to select it.",
+ "position": "top",
+ "stepInteraction": true,
+ "interaction": "select-task",
+ "targetTaskName": "Greet"
+ },
+ {
+ "selector": "[data-window-id=\"context-panel\"]",
+ "position": "left",
+ "highlightedSelectors": [
+ "[data-window-id=\"context-panel\"]",
+ "[data-dock-window-content=\"context-panel\"]"
+ ],
+ "mutationObservables": [
+ "[data-window-id=\"context-panel\"]",
+ "[data-dock-window-content=\"context-panel\"]"
+ ],
+ "resizeObservables": [
+ "[data-window-id=\"context-panel\"]",
+ "[data-dock-window-content=\"context-panel\"]"
+ ],
+ "targetWindowId": "context-panel",
+ "content": "Selecting a task opens its Task Properties panel here in the right sidebar. From this panel you can edit input arguments, configure node settings, define annotations, and inspect the component spec."
+ },
+ {
+ "selector": "[data-window-id=\"context-panel\"]",
+ "content": "Windows are flexible. Try grabbing the Task Properties header and dragging it out of the dock to float it as its own window.",
+ "position": "left",
+ "stepInteraction": true,
+ "interaction": "undock-window",
+ "targetWindowId": "context-panel",
+ "targetWindowName": "Task Properties",
+ "fallbackContent": "Windows are flexible. Try grabbing the Task Properties header and dragging it around the canvas."
+ },
+ {
+ "selector": "[data-window-id=\"context-panel\"]",
+ "content": "Nice! Now drag that floating window back onto the left or right sidebar to re-dock it.",
+ "position": "left",
+ "stepInteraction": true,
+ "interaction": "redock-window",
+ "targetWindowId": "context-panel",
+ "targetWindowName": "Task Properties",
+ "fallbackContent": "Windows can be docked in either sidebar. Create the perfect layout that suits you!"
+ },
+ {
+ "selector": "[data-tracking-id=\"v2.pipeline_editor.windows_menu\"]",
+ "highlightedSelectors": [
+ "[data-tracking-id=\"v2.pipeline_editor.windows_menu\"]",
+ "[data-tour=\"windows-menu-content\"]",
+ "[data-tour=\"windows-menu-submenu-content\"]"
+ ],
+ "mutationObservables": [
+ "[data-tour=\"windows-menu-content\"]",
+ "[data-tour=\"windows-menu-submenu-content\"]"
+ ],
+ "content": "Last one. If you ever need to reconfigure your layout, the Windows menu lets you toggle panels on or off and apply a layout preset.",
+ "position": "right",
+ "stepInteraction": true
+ }
+ ]
+}
diff --git a/src/routes/v2/pages/Editor/EditorV2.tsx b/src/routes/v2/pages/Editor/EditorV2.tsx
index 3989bd85d..070daffef 100644
--- a/src/routes/v2/pages/Editor/EditorV2.tsx
+++ b/src/routes/v2/pages/Editor/EditorV2.tsx
@@ -122,7 +122,10 @@ const PipelineEditor = withSuspenseWrapper(
data-editor-ready="true"
>