diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..d444e68
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,10 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(rg:*)",
+ "Bash(grep:*)",
+ "Bash(npm run compile:*)"
+ ],
+ "deny": []
+ }
+}
\ No newline at end of file
diff --git a/.github/workflows/build-extensions.yml b/.github/workflows/build-extensions.yml
index 63a850b..7f3f9cc 100644
--- a/.github/workflows/build-extensions.yml
+++ b/.github/workflows/build-extensions.yml
@@ -41,9 +41,9 @@ jobs:
- name: Build theme extension
run: |
echo "Building theme extension..."
- cd themes && npx @vscode/vsce package --out ../zenml-color-theme.vsix
+ cd themes && npx @vscode/vsce package --out ./zenml-color-theme-0.0.1.vsix
- - name: Verify extensions were built
+ - name: Verify extensions were builts
run: |
echo "Checking built extensions..."
if [ -d ".devcontainer/extensions" ]; then
@@ -54,11 +54,11 @@ jobs:
exit 1
fi
- if [ -f "zenml-color-theme.vsix" ]; then
+ if [ -f "themes/zenml-color-theme-0.0.1.vsix" ]; then
echo "Theme extension built successfully:"
- ls -la zenml-color-theme.vsix
+ ls -la themes/zenml-color-theme-0.0.1.vsix
else
- echo "ERROR: zenml-color-theme.vsix not found"
+ echo "ERROR: zenml-color-theme-0.0.1.vsix not found"
exit 1
fi
@@ -72,7 +72,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: theme-extension
- path: zenml-color-theme.vsix
+ path: themes/zenml-color-theme-0.0.1.vsix
- name: Commit and push built extensions
# Only commit on push to main/develop branches, not on PRs
@@ -83,7 +83,7 @@ jobs:
# Add the built extensions to git
git add .devcontainer/extensions/zenml-codespace-tutorial-*.vsix
- git add zenml-color-theme.vsix
+ git add themes/zenml-color-theme-0.0.1.vsix
# Check if there are any changes to commit
if git diff --staged --quiet; then
diff --git a/assets/main.css b/assets/main.css
index ac44b1d..73f7bd6 100644
--- a/assets/main.css
+++ b/assets/main.css
@@ -319,7 +319,7 @@ header {
}
.tutorial-title h2 {
- font-size: 1rem;
+ font-size: 1.25rem;
font-weight: 600;
color: var(--zenml-dark);
margin: 0;
@@ -448,6 +448,13 @@ footer .run-pipeline-button.running {
footer .run-pipeline-button.completed {
background: var(--zenml-green);
color: white;
+ cursor: default;
+}
+
+footer .run-pipeline-button.completed:hover {
+ background: var(--zenml-green);
+ transform: none;
+ box-shadow: none;
}
footer .run-pipeline-button.failed {
@@ -455,6 +462,54 @@ footer .run-pipeline-button.failed {
color: white;
}
+
+/* Pipeline button group */
+.pipeline-button-group {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+}
+
+/* Dashboard button styling - smaller variant */
+.dashboard-button-small {
+ background: var(--vscode-button-secondaryBackground);
+ border: 1px solid var(--vscode-sideBar-border);
+ color: var(--vscode-button-secondaryForeground);
+ padding: 9px 16px;
+ border-radius: 6px;
+ font-size: 14px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ justify-content: center;
+ font-family: "Inter", sans-serif;
+ margin: 0;
+ height: 38px;
+ text-decoration: none;
+ flex-shrink: 0;
+ white-space: nowrap;
+ box-sizing: border-box;
+}
+
+.dashboard-button-small:hover {
+ background: var(--vscode-button-secondaryHoverBackground);
+ transform: translateY(-1px);
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
+ color: var(--vscode-button-secondaryForeground);
+ text-decoration: none;
+}
+
+.dashboard-button-small:active {
+ transform: translateY(0);
+}
+
+.dashboard-button-small .codicon {
+ font-size: 14px;
+}
+
#progress {
height: 4px;
background-color: var(--vscode-progressBar-background);
diff --git a/assets/main.js b/assets/main.js
index f609a1f..b2b0031 100644
--- a/assets/main.js
+++ b/assets/main.js
@@ -476,7 +476,8 @@
} else if (status === "completed" || status === "cached") {
runButton.innerHTML = `ā ${buttonTexts[status]}`;
//@ts-ignore
- runButton.disabled = false;
+ runButton.disabled = true;
+ runButton.style.cursor = "default";
} else if (status === "failed") {
runButton.innerHTML = ` ${buttonTexts[status]}`;
//@ts-ignore
@@ -493,14 +494,18 @@
}
function showDashboardUrl(url) {
- const dashboardLink = document.getElementById("dashboard-link");
- const dashboardUrl = document.getElementById("dashboard-url");
+ const dashboardButton = document.getElementById("dashboard-button");
- if (dashboardLink && dashboardUrl) {
+ if (dashboardButton) {
//@ts-ignore
- dashboardUrl.href = url;
- dashboardUrl.textContent = "View Pipeline in Dashboard";
- dashboardLink.style.display = "flex";
+ dashboardButton.href = url;
+ dashboardButton.style.display = "flex";
+
+ // Add click handler to open in external browser
+ dashboardButton.addEventListener('click', function(e) {
+ e.preventDefault();
+ vscode.postMessage({ type: "openDashboard", url: url });
+ });
}
}
diff --git a/pipelines/caching/cache_pipeline.py b/pipelines/caching/cache_pipeline.py
index 106786d..19277ec 100644
--- a/pipelines/caching/cache_pipeline.py
+++ b/pipelines/caching/cache_pipeline.py
@@ -11,9 +11,8 @@
@step(enable_cache=True)
def slow_step() -> Annotated[int, "answer"]:
- logger.info("Starting slow computation (3 seconds)...")
+ logger.info("š Actually computing result... (sleeping 3 seconds)")
time.sleep(3)
- logger.info("Computation completed!")
return 42
@@ -23,10 +22,17 @@ def cache_pipeline():
if __name__ == "__main__":
- logger.info("First run - will take ~3 seconds")
- cache_pipeline() # first run ~3 s
-
- logger.info("Second run - should be instant (cache hit)")
- cache_pipeline() # second run instant (cache hit)
+ logger.info("\n" + "="*60)
+ logger.info(">>> RUN 1: First execution (no cache available)")
+ logger.info("="*60)
+ cache_pipeline()
+
+ logger.info("\n" + "="*60)
+ logger.info(">>> RUN 2: Second execution (cache should be used)")
+ logger.info("="*60)
+ cache_pipeline()
+
+ logger.info("\nš” Notice: The step's log message only appears in Run 1!")
+ logger.info(" In Run 2, the step was skipped entirely due to caching.")
log_dashboard_urls("cache_pipeline")
diff --git a/pipelines/caching/caching.md b/pipelines/caching/caching.md
index 1d89a4f..06ad35b 100644
--- a/pipelines/caching/caching.md
+++ b/pipelines/caching/caching.md
@@ -1,7 +1,3 @@
-# Caching - Smart Re-runs
-
-Learn how ZenML's caching system can save you time by avoiding redundant computations.
-
## What you'll learn
- How to enable caching on steps
@@ -28,4 +24,5 @@ def slow_step() -> Annotated[int, "answer"]:
## Try it yourself
-Run this pipeline twice! The first run takes ~3 seconds, the second is instant thanks to caching.
\ No newline at end of file
+Run this pipeline twice! The first run takes ~3 seconds, the second is instant
+thanks to caching.
diff --git a/pipelines/completion/completion.md b/pipelines/completion/completion.md
index 1f7d7a0..afebd6d 100644
--- a/pipelines/completion/completion.md
+++ b/pipelines/completion/completion.md
@@ -14,7 +14,7 @@
You Completed the Tutorial
-You've successfully completed the **ZenML Interactive Tutorial** and mastered the fundamentals of MLOps with ZenML!
+You've successfully completed the **ZenML Interactive Tutorial** and mastered the fundamentals of using ZenML for MLOps!
diff --git a/pipelines/fanOut/fanOut.md b/pipelines/fanOut/fanOut.md
index 2a981dc..c15ffa5 100644
--- a/pipelines/fanOut/fanOut.md
+++ b/pipelines/fanOut/fanOut.md
@@ -1,7 +1,3 @@
-# Fan-out/Fan-in - Parallel Processing
-
-Learn how to create parallel workflows that split work across multiple steps and then combine the results.
-
## What you'll learn
- How to create parallel processing patterns
@@ -29,4 +25,5 @@ def fan_pipeline(parallel: int = 4):
## Try it yourself
-Run this pipeline to see how it processes data in parallel across multiple steps, then combines the results!
\ No newline at end of file
+Run this pipeline to see how it processes data in parallel across multiple
+steps, then combines the results!
diff --git a/pipelines/fanOut/fan_pipeline.py b/pipelines/fanOut/fan_pipeline.py
index c2f92a9..35c1a20 100644
--- a/pipelines/fanOut/fan_pipeline.py
+++ b/pipelines/fanOut/fan_pipeline.py
@@ -1,11 +1,3 @@
-"""
-Shows parallel fan-out (multiple identical steps) and a fan-in step that
-gathers all their outputs via the Client API.
-
-Run it once and observe the printed summary. Re-run: cache is OFF so you
-see the steps execute every time.
-"""
-
from typing_extensions import Annotated
from zenml import get_step_context, pipeline, step
from zenml.client import Client
diff --git a/pipelines/helloWorld/helloWorld.md b/pipelines/helloWorld/helloWorld.md
index e510199..a3edffe 100644
--- a/pipelines/helloWorld/helloWorld.md
+++ b/pipelines/helloWorld/helloWorld.md
@@ -1,5 +1,3 @@
-# Hello World - Steps & Pipelines
-
Welcome to your first ZenML pipeline! This tutorial introduces the fundamental concepts of **steps** and **pipelines**.
## What you'll learn
@@ -30,4 +28,5 @@ def hello_pipeline():
## Try it yourself
-Click the **Run Pipeline** button to execute your first ZenML pipeline and see the output!
\ No newline at end of file
+Click the **Run Pipeline** button to execute your first ZenML pipeline and see
+the output!
diff --git a/pipelines/metadata/metadata.md b/pipelines/metadata/metadata.md
index 36d9674..a5238ce 100644
--- a/pipelines/metadata/metadata.md
+++ b/pipelines/metadata/metadata.md
@@ -1,7 +1,3 @@
-# Metadata - Recording Useful Facts
-
-Learn how to log metadata to track important information about your pipeline runs.
-
## What you'll learn
- How to use `log_metadata()` to record key information
@@ -29,4 +25,4 @@ def compute_accuracy() -> Annotated[float, "accuracy_metric"]:
## Try it yourself
-Run this pipeline and check the ZenML dashboard to see the metadata card!
\ No newline at end of file
+Run this pipeline and check the ZenML dashboard to see the metadata card!
diff --git a/pipelines/parameters/parameters.md b/pipelines/parameters/parameters.md
index 64fc8d0..ac05558 100644
--- a/pipelines/parameters/parameters.md
+++ b/pipelines/parameters/parameters.md
@@ -1,7 +1,3 @@
-# Parameters - Configurable Behavior
-
-Learn how to make your pipelines configurable with parameters.
-
## What you'll learn
- How to add parameters to steps and pipelines
@@ -31,4 +27,4 @@ def param_pipeline(number: int = 3, factor: int = 2):
## Try it yourself
-Run this pipeline to see how parameters control the multiplication operation!
\ No newline at end of file
+Run this pipeline to see how parameters control the multiplication operation!
diff --git a/pipelines/retries/retries.md b/pipelines/retries/retries.md
index 59aaef6..80affb7 100644
--- a/pipelines/retries/retries.md
+++ b/pipelines/retries/retries.md
@@ -1,7 +1,3 @@
-# Retries & Hooks - Robust Pipelines
-
-Learn how to build resilient pipelines that can handle failures gracefully with automatic retries and hooks.
-
## What you'll learn
- How to configure automatic retries for flaky steps
@@ -32,4 +28,5 @@ def flaky() -> Annotated[str, "result"]:
## Try it yourself
-Run this pipeline multiple times! Sometimes it succeeds immediately, sometimes it needs retries to handle the random failures.
\ No newline at end of file
+Run this pipeline multiple times! Sometimes it succeeds immediately, sometimes
+it needs retries to handle the random failures.
diff --git a/pipelines/retries/robust_pipeline.py b/pipelines/retries/robust_pipeline.py
index 69efa63..24d4b5f 100644
--- a/pipelines/retries/robust_pipeline.py
+++ b/pipelines/retries/robust_pipeline.py
@@ -1,8 +1,3 @@
-"""
-Demonstrates automatic retries *and* a failure hook.
-Run it a few times ā roughly half will need 1ā2 retries.
-"""
-
import random
import time
diff --git a/pipelines/stepIO/stepIO.md b/pipelines/stepIO/stepIO.md
index 5a70396..1c3058f 100644
--- a/pipelines/stepIO/stepIO.md
+++ b/pipelines/stepIO/stepIO.md
@@ -1,7 +1,3 @@
-# Step I/O - Typed Inputs and Outputs
-
-Now let's learn how to pass data between steps with proper type annotations.
-
## What you'll learn
- How to define typed step outputs using `Annotated` types
@@ -37,4 +33,5 @@ def count_rows(
## Try it yourself
-Run this pipeline to see how data flows from the `load_data` step to the `count_rows` step!
\ No newline at end of file
+Run this pipeline to see how data flows from the `load_data` step to the
+`count_rows` step!
diff --git a/pipelines/tagging/tagged_pipeline.py b/pipelines/tagging/tagged_pipeline.py
index d2b86c9..e353c49 100644
--- a/pipelines/tagging/tagged_pipeline.py
+++ b/pipelines/tagging/tagged_pipeline.py
@@ -1,12 +1,3 @@
-"""
-Artifact Tagging Demonstration
-This example shows different ways to tag artifacts in ZenML:
-1. Regular tags on artifacts using ArtifactConfig
-2. Cascade tags from pipeline to artifacts
-3. Dynamic tagging within steps
-4. Filtering artifacts by tags
-"""
-
from typing import Annotated
import pandas as pd
diff --git a/pipelines/tagging/tagging.md b/pipelines/tagging/tagging.md
index aea40f2..098dfbe 100644
--- a/pipelines/tagging/tagging.md
+++ b/pipelines/tagging/tagging.md
@@ -1,7 +1,3 @@
-# Tagging - Keep Runs Organized
-
-Learn how to use tags to organize and categorize your pipeline runs.
-
## What you'll learn
- How to add simple tags to pipelines
@@ -27,4 +23,5 @@ def tagged_pipeline():
## Try it yourself
-Run this pipeline multiple times and watch how the exclusive tag automatically moves to the latest run!
\ No newline at end of file
+Run this pipeline multiple times and watch how the exclusive tag automatically
+moves to the latest run!
diff --git a/pipelines/visualizations/visualizations-dashboard.png b/pipelines/visualizations/visualizations-dashboard.png
new file mode 100644
index 0000000..c67235a
Binary files /dev/null and b/pipelines/visualizations/visualizations-dashboard.png differ
diff --git a/pipelines/visualizations/visualizations.md b/pipelines/visualizations/visualizations.md
index 8de6779..58b0adf 100644
--- a/pipelines/visualizations/visualizations.md
+++ b/pipelines/visualizations/visualizations.md
@@ -1,7 +1,3 @@
-# Visualizations - Custom Charts
-
-Learn how to create automatic and custom visualizations that appear in the ZenML dashboard.
-
## What you'll learn
- How ZenML automatically visualizes common data types
@@ -30,6 +26,9 @@ def scatter(df: pd.DataFrame) -> Annotated[HTMLString, "scatter_plot"]:
- **HTMLString type** lets you embed rich content in the dashboard
- **Dashboard integration** makes results easily shareable
+
+
## Try it yourself
-Run this pipeline and check the dashboard to see both the automatic DataFrame visualization and the custom scatter plot!
\ No newline at end of file
+Run this pipeline and check the dashboard to see both the automatic DataFrame
+visualization and the custom scatter plot!
diff --git a/pipelines/welcome/welcome.md b/pipelines/welcome/welcome.md
index ad5e630..f1b9e4c 100644
--- a/pipelines/welcome/welcome.md
+++ b/pipelines/welcome/welcome.md
@@ -1,7 +1,7 @@
-# Welcome to ZenML Interactive Tutorial!
+# Welcome to the ZenML Interactive Tutorial!
This hands-on tutorial will teach you **ZenML fundamentals** through 10 progressive lessons. Each lesson introduces exactly **one new concept** with copy-paste code examples.
diff --git a/src/tutorialOrchestrator.ts b/src/tutorialOrchestrator.ts
index 94a5ec1..bc7ef69 100644
--- a/src/tutorialOrchestrator.ts
+++ b/src/tutorialOrchestrator.ts
@@ -27,8 +27,14 @@ export default class TutorialOrchestrator {
private _getDashboardUrl(runId?: string): string {
const baseUrl = vscode.workspace
.getConfiguration("zenml")
- .get("dashboardUrl", "http://localhost:8080");
- return runId ? `${baseUrl}/workspaces/default/runs/${runId}` : baseUrl;
+ .get("dashboardUrl", "https://cloud.zenml.io");
+ // If we have a run ID, show specific run, otherwise show pipelines page
+ if (runId) {
+ return `${baseUrl}/workspaces/default/runs/${runId}`;
+ } else {
+ // Fallback to generic pipelines page when no specific run ID
+ return `${baseUrl}/workspaces/default/pipelines`;
+ }
}
constructor(context: vscode.ExtensionContext, tutorial: Tutorial) {
@@ -142,7 +148,7 @@ export default class TutorialOrchestrator {
codeRunner(
this.terminal,
this._codePanel.document.uri,
- () => {
+ (dashboardUrl?: string) => {
vscode.window.showInformationMessage("Code Ran Successfully! š");
if (callback) {
callback();
@@ -196,7 +202,7 @@ export default class TutorialOrchestrator {
codeRunner(
this.terminal,
this._codePanel.document.uri,
- (runId?: string) => {
+ (dashboardUrl?: string) => {
// Pipeline completed successfully
this._pipelineRunning = false;
this._completedTutorials.add(this._tutorial.currentSection.index);
@@ -204,19 +210,17 @@ export default class TutorialOrchestrator {
type: "pipelineStatusUpdate",
status: "completed",
});
- this._sendWebviewMessage({ type: "pipelineCompleted", runId: runId });
+ this._sendWebviewMessage({ type: "pipelineCompleted" });
// Save progress
this._saveProgress();
- // Show dashboard URL
- if (runId) {
- const dashboardUrl = this._getDashboardUrl(runId);
- this._sendWebviewMessage({
- type: "showDashboardUrl",
- url: dashboardUrl,
- });
- }
+ // Show dashboard URL - use the captured URL or fallback to generic
+ const finalDashboardUrl = dashboardUrl || this._getDashboardUrl();
+ this._sendWebviewMessage({
+ type: "showDashboardUrl",
+ url: finalDashboardUrl,
+ });
},
() => {
// Pipeline failed
@@ -779,10 +783,16 @@ export default class TutorialOrchestrator {
Prev
-
+