diff --git a/docs/getting-started/try-it-out/on-k3d-locally.mdx b/docs/getting-started/try-it-out/on-k3d-locally.mdx
index e34daf50..90a6f786 100644
--- a/docs/getting-started/try-it-out/on-k3d-locally.mdx
+++ b/docs/getting-started/try-it-out/on-k3d-locally.mdx
@@ -10,39 +10,46 @@ import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import { versions, defaultCredentials } from "../../_constants.mdx";
import AgentSetupBuilder from "@site/src/components/AgentSetupBuilder";
-import AgentCallout from "@site/src/components/AgentCallout";
+import {
+ SetupSwitch,
+ SetupAgent,
+ SetupManual,
+} from "@site/src/components/SetupSwitch";
# Run OpenChoreo on K3d Locally
-This guide walks you through setting up OpenChoreo on your machine with k3d. You will install each plane one at a time, and after each one you will do something real with it: log in, deploy a service, or trigger a build.
+This guide runs all four OpenChoreo planes on your machine, in a single k3d cluster.
OpenChoreo has four planes:
- **Control Plane** runs the API, console, identity provider, and controllers.
- **Data Plane** runs your workloads and routes traffic to them.
- **Workflow Plane** builds container images from source using Argo Workflows.
-- **Observability Plane** collects logs and metrics from all other planes.
+- **Observability Plane** collects logs and metrics from the other planes.
-By the end you will have all four running in a single k3d cluster.
+By the end you'll have a working installation on localhost: a web app you can open in your browser, a source-to-image build pipeline, and log collection.
-**What you will get:**
+Pick how you want to install it:
-- A working OpenChoreo installation on localhost
-- A deployed web app you can open in your browser
-- A source-to-image build pipeline
-- Log collection and querying
+
-
+
-Install the skill, pick your planes, and paste the prompt into your agent (Claude Code, Codex, Cursor, or any coding agent).
+Install the `openchoreo-setup` skill.
```bash
-npx skills add openchoreo/skills --skill openchoreo-setup
+npx skills add openchoreo/skills --skill openchoreo-setup -g
```
+Then pick your planes and copy the prompt into your agent.
+
-
+
+
+
+
+Install OpenChoreo by hand, one plane at a time. Start with the prerequisites, then work through each step in order.
## Prerequisites
@@ -1002,3 +1009,7 @@ k3d cluster delete openchoreo
- Explore the sample applications
- Read the [Deployment Topology](../../platform-engineer-guide/deployment-topology.mdx) guide for production setups
- Learn about [Multi-Cluster Connectivity](../../platform-engineer-guide/multi-cluster-connectivity.mdx) for separating planes across clusters
+
+
+
+
diff --git a/docs/getting-started/try-it-out/on-your-environment.mdx b/docs/getting-started/try-it-out/on-your-environment.mdx
index 9db540b0..baed4c26 100644
--- a/docs/getting-started/try-it-out/on-your-environment.mdx
+++ b/docs/getting-started/try-it-out/on-your-environment.mdx
@@ -8,41 +8,48 @@ import CodeBlock from "@theme/CodeBlock";
import Link from "@docusaurus/Link";
import { versions, defaultCredentials } from "../../_constants.mdx";
import AgentSetupBuilder from "@site/src/components/AgentSetupBuilder";
-import AgentCallout from "@site/src/components/AgentCallout";
+import {
+ SetupSwitch,
+ SetupAgent,
+ SetupManual,
+} from "@site/src/components/SetupSwitch";
# Run OpenChoreo in Your Environment
-This guide walks you through setting up OpenChoreo on any Kubernetes cluster (k3s, GKE, EKS, DOKS, AKS, or self-managed). You will install each plane one at a time, and after each one you will do something real with it: log in, deploy a service, or trigger a build.
+This guide runs OpenChoreo on any Kubernetes cluster (k3s, GKE, EKS, DOKS, AKS, or self-managed), using a **single-cluster topology** with all planes in one cluster. For split-cluster setups, follow [Multi-Cluster Connectivity](../../platform-engineer-guide/multi-cluster-connectivity.mdx).
-It uses a **single-cluster topology** (all planes in one cluster). For split-cluster setups, follow [Multi-Cluster Connectivity](../../platform-engineer-guide/multi-cluster-connectivity.mdx).
-
-All gateways are configured with HTTPS using self-signed certificates by default. You can replace them with certificates from a real CA later.
+Gateways use HTTPS with self-signed certificates by default; you can swap in certificates from a real CA later.
OpenChoreo has four planes:
- **Control Plane** runs the API, console, identity provider, and controllers.
- **Data Plane** runs your workloads and routes traffic to them.
- **Workflow Plane** builds container images from source using Argo Workflows.
-- **Observability Plane** collects logs and metrics from all other planes.
+- **Observability Plane** collects logs and metrics from the other planes.
+
+By the end you'll have OpenChoreo running on your cluster over HTTPS, reachable at a console URL through your LoadBalancer, with a web app deployed and, optionally, a source-to-image build pipeline and log collection.
-**What you will get:**
+Pick how you want to install it:
-- OpenChoreo running on your Kubernetes cluster with HTTPS
-- A reachable console URL over your cluster LoadBalancer
-- A deployed web app you can open in your browser
-- Optional source-to-image build pipeline and log collection
+
-
+
-Install the skill, pick your environment and planes, and paste the prompt into your agent (Claude Code, Codex, Cursor, or any coding agent).
+Install the `openchoreo-setup` skill.
```bash
-npx skills add openchoreo/skills --skill openchoreo-setup
+npx skills add openchoreo/skills --skill openchoreo-setup -g
```
+Then pick your environment and planes, and copy the prompt into your agent.
+
-
+
+
+
+
+Install OpenChoreo by hand, one plane at a time. Start with the prerequisites, then work through each step in order.
## Prerequisites
@@ -1253,3 +1260,7 @@ kubectl delete namespace \
- Follow [Deploy and Explore](../deploy-and-explore.mdx) to understand the resources OpenChoreo creates
- Explore the sample applications
- Move to multi-cluster isolation using [Multi-Cluster Connectivity](../../platform-engineer-guide/multi-cluster-connectivity.mdx)
+
+
+
+
diff --git a/plugins/docusaurus-plugin-markdown-export/mdxProcessor.js b/plugins/docusaurus-plugin-markdown-export/mdxProcessor.js
index f7f320aa..a6135474 100644
--- a/plugins/docusaurus-plugin-markdown-export/mdxProcessor.js
+++ b/plugins/docusaurus-plugin-markdown-export/mdxProcessor.js
@@ -60,6 +60,11 @@ async function processMarkdownFile(content, constants, sourceDir, linkContext) {
const { frontmatter, body } = extractFrontmatter(result);
result = body;
+ // Step 2.5: Unwrap the interactive setup-switch. Drop the agent panel
+ // (interactive prompt builder — not meaningful as markdown) and strip the
+ // switch/manual container tags so the manual guide stays as clean markdown.
+ result = processSetupSwitch(result);
+
// Step 3: Process CodeBlock components
result = processCodeBlocks(result, constants);
@@ -118,6 +123,18 @@ function extractFrontmatter(content) {
return { frontmatter, body };
}
+function processSetupSwitch(content) {
+ let result = content;
+ // Remove the entire agent panel (prompt builder, npx, etc.).
+ result = result.replace(/[\s\S]*?<\/SetupAgent>/g, '');
+ // Unwrap the switch + manual containers, keeping their markdown children.
+ result = result.replace(/<\/?SetupSwitch>/g, '');
+ result = result.replace(/<\/?SetupManual>/g, '');
+ // Drop any stray self-closing component tags (e.g. the plane builder).
+ result = result.replace(/]*\/>/g, '');
+ return result;
+}
+
function removeImports(content) {
// Remove all import statements (single and multi-line)
return content.replace(/^import\s+[\s\S]*?from\s+['"][^'"]+['"];?\s*$/gm, '');
diff --git a/src/components/AgentCallout/styles.module.css b/src/components/AgentCallout/styles.module.css
index 9b15aa9d..c3b32dcd 100644
--- a/src/components/AgentCallout/styles.module.css
+++ b/src/components/AgentCallout/styles.module.css
@@ -47,3 +47,13 @@
.content {
margin-top: 0.75rem;
}
+
+.content :global([class*="codeBlockContainer"]) {
+ background: var(--ifm-background-surface-color);
+ border: 1px solid var(--ifm-color-emphasis-300);
+ box-shadow: none;
+}
+
+.content :global(.prism-code) {
+ background: var(--ifm-background-surface-color);
+}
diff --git a/src/components/AgentSetupBuilder/index.tsx b/src/components/AgentSetupBuilder/index.tsx
index 118b3857..99f891c3 100644
--- a/src/components/AgentSetupBuilder/index.tsx
+++ b/src/components/AgentSetupBuilder/index.tsx
@@ -139,9 +139,11 @@ export default function AgentSetupBuilder({ currentVersion, fixedEnv }: Props) {
)}
- {p.label}
+
+ {p.label}
+ {p.required && (required)}
+ {p.desc}
- {p.required && required}
);
})}
diff --git a/src/components/AgentSetupBuilder/styles.module.css b/src/components/AgentSetupBuilder/styles.module.css
index 7c1d8d31..63b30d02 100644
--- a/src/components/AgentSetupBuilder/styles.module.css
+++ b/src/components/AgentSetupBuilder/styles.module.css
@@ -1,7 +1,7 @@
.builder {
display: flex;
flex-direction: column;
- gap: 0.75rem;
+ gap: 1rem;
margin-top: 0.75rem;
}
@@ -64,7 +64,7 @@
.planesGrid {
display: grid;
grid-template-columns: 1fr 1fr;
- gap: 0.4rem;
+ gap: 0.55rem;
}
@media (max-width: 480px) {
@@ -75,11 +75,11 @@
position: relative;
display: flex;
flex-direction: column;
- gap: 0.1rem;
- padding: 0.5rem 0.5rem 0.5rem 2rem;
+ gap: 0.2rem;
+ padding: 0.7rem 0.75rem 0.7rem 2.1rem;
border-radius: 8px;
- border: 1px solid var(--ifm-color-emphasis-200);
- background: var(--ifm-color-emphasis-100);
+ border: 1px solid var(--ifm-color-emphasis-300);
+ background: var(--ifm-background-surface-color);
cursor: pointer;
text-align: left;
transition: border-color 0.12s, background 0.12s, opacity 0.12s;
@@ -87,14 +87,13 @@
.planeCardRequired {
cursor: default;
- opacity: 0.55;
}
.planeCard:not(.planeCardRequired):hover {
border-color: var(--ifm-color-primary);
}
-.planeCardOn:not(.planeCardRequired) {
+.planeCardOn {
border-color: var(--ifm-color-primary);
background: rgba(43, 140, 247, 0.08);
}
@@ -133,10 +132,9 @@
color: var(--ifm-color-emphasis-600);
}
-.planeReqBadge {
- font-size: 0.65rem;
- color: var(--ifm-color-emphasis-500);
- font-style: italic;
+.planeReqInline {
+ font-weight: 400;
+ color: var(--ifm-color-emphasis-600);
}
.builder :global(.prism-code) {
diff --git a/src/components/SetupSwitch/index.tsx b/src/components/SetupSwitch/index.tsx
new file mode 100644
index 00000000..5852ddbd
--- /dev/null
+++ b/src/components/SetupSwitch/index.tsx
@@ -0,0 +1,93 @@
+import React, { createContext, useContext, useEffect, useRef, useState } from "react";
+import styles from "./styles.module.css";
+
+// Split switch for the install pages. Both panels stay in the DOM and toggle
+// via `hidden` (not unmounted) so the manual guide stays crawlable.
+
+type Choice = "agent" | "manual";
+
+const SwitchCtx = createContext<{ sel: Choice; setSel: (s: Choice) => void }>({
+ sel: "agent",
+ setSel: () => {},
+});
+
+export function SetupSwitch({ children }: { children: React.ReactNode }) {
+ const [sel, setSel] = useState("agent");
+
+ // Expose the active pane on so the page TOC (rendered outside this
+ // component) can hide the manual headings while the agent panel is showing.
+ useEffect(() => {
+ const root = document.documentElement;
+ root.setAttribute("data-setup-pane", sel);
+ return () => root.removeAttribute("data-setup-pane");
+ }, [sel]);
+
+ return (
+
+
+
+
+
+ {children}
+
+ );
+}
+
+export function SetupAgent({ children }: { children: React.ReactNode }) {
+ const { sel } = useContext(SwitchCtx);
+ return
{children}
;
+}
+
+export function SetupManual({ children }: { children: React.ReactNode }) {
+ const { sel, setSel } = useContext(SwitchCtx);
+ const ref = useRef(null);
+ const pendingId = useRef(null);
+
+ // A deep link or TOC click to a manual heading should reveal this panel.
+ useEffect(() => {
+ const reveal = () => {
+ const id = decodeURIComponent(window.location.hash.slice(1));
+ if (!id || !ref.current) return;
+ const target = document.getElementById(id);
+ if (target && ref.current.hidden && ref.current.contains(target)) {
+ pendingId.current = id;
+ setSel("manual");
+ }
+ };
+ reveal();
+ window.addEventListener("hashchange", reveal);
+ return () => window.removeEventListener("hashchange", reveal);
+ }, [setSel]);
+
+ useEffect(() => {
+ if (sel === "manual" && pendingId.current) {
+ const target = document.getElementById(pendingId.current);
+ pendingId.current = null;
+ if (target) target.scrollIntoView();
+ }
+ }, [sel]);
+
+ return (
+