Add sequential workshop tutorial format (pick-and-place seed)#5104
Add sequential workshop tutorial format (pick-and-place seed)#5104Nick Hehr (HipsterBrown) wants to merge 44 commits into
Conversation
✅ Deploy Preview for viam-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
f9c42ec to
c324bc2
Compare
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0138T5LK4cgYNgn3X54V7iZq
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0138T5LK4cgYNgn3X54V7iZq
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0138T5LK4cgYNgn3X54V7iZq
…gation The tutorials sidebar is a minimal, per-section-cached skeleton that does not render a section tree, so workshop phases were not navigable from phase pages. Add a workshop-phases shortcode (full phase list, current highlighted) at the top of each phase page and the template.
Take the overview and five phase pages out of Hugo draft mode so they render in the Netlify deploy preview (which builds without --buildDrafts). The headless _phase-template.md stays draft. Note: phase-page exclusion from the production Typesense card grid remains a tracked follow-up.
The overview Phases list and the Prerequisites Phase 1 references were plain text, so there was no way to start the workshop from the overview. Link each phase to its page.
Populate the previously-empty Site.Menus.main by adding a menu.main entry to the tutorials section front matter. Hugo associates the entry with the page, so the navbar resolves the href to /tutorials/ and applies the active class on every /tutorials/... page. The sidebar (toc_hide) and footer are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0138T5LK4cgYNgn3X54V7iZq
Creates docs/tutorials/all/_index.md and layouts/docs/tutorials-all.html so the Typesense filter UI and dev archive fallback are available at the new /tutorials/all/ URL, without touching the existing /tutorials/ landing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MZUbnKMDUmYy1iRkxkpjyz
Resolve the Tutorials top-nav item rendering white-on-white by raising the project rule specificity above Bootstrap's .navbar-dark nav-link color (covers base, active, hover, focus). Align Tutorials next to the brand with a separator matched to the brand divider height, absolutely center the search input in the navbar, and remove the nav-link hover state to match the static brand link. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MZUbnKMDUmYy1iRkxkpjyz
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MZUbnKMDUmYy1iRkxkpjyz
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MZUbnKMDUmYy1iRkxkpjyz
…hive link Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MZUbnKMDUmYy1iRkxkpjyz
…ghter workshop scope Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MZUbnKMDUmYy1iRkxkpjyz
Create layouts/partials/sidebar.html as a project override that branches on .Params.workshop: workshop pages get a targeted sidebar-workshop.html partial listing all phases with the current one highlighted; all other pages fall through to a verbatim copy of the Docsy default sidebar logic, preserving existing behaviour site-wide. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MZUbnKMDUmYy1iRkxkpjyz
…ase numbers
Restore the else-branch jQuery in sidebar.html to match Docsy's default
byte-for-byte (active-path walks from #{{ $mid }}, not #{{ $mid }}-li),
and remove the redundant {{ add $i 1 }}. prefix in sidebar-workshop.html
since phase linkTitles already include their numbers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZUbnKMDUmYy1iRkxkpjyz
…version note Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MZUbnKMDUmYy1iRkxkpjyz
…ide) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MZUbnKMDUmYy1iRkxkpjyz
|
|
||
| - **The Viam cloud app** is the source of truth for configuration. When you add a component, change an attribute, or wire up a service, you are editing a JSON document stored in the cloud. The app never runs your robot directly; it describes what should run. | ||
| - **viam-agent** runs on the machine itself. It manages the install: it installs, updates, and keeps `viam-server` running, restarts it if it crashes, and provides the initial bootstrap credentials viam-server needs to reach the cloud. Think of viam-agent as the process supervisor, not as the source of your resource config, and not as something you interact with directly during this workshop. | ||
| - **viam-server** is the process that does the actual work. It pulls its resource config from the cloud app, starts every component and service that config describes, and exposes them over an API. This is the layer that drives the arm, reads the camera, and runs the vision pipeline. |
There was a problem hiding this comment.
| - **viam-server** is the process that does the actual work. It pulls its resource config from the cloud app, starts every component and service that config describes, and exposes them over an API. This is the layer that drives the arm, reads the camera, and runs the vision pipeline. | |
| - **viam-server** pulls the machine resource configuration from the cloud app, starts every component and service that configuration describes, and exposes the components and services over an API. This is the layer that handles the modules that drive the arm, reads the camera, and runs the vision pipeline. |
|
|
||
| ## How your SDK connects | ||
|
|
||
| In Phase 4 you write a Python script that imports the Viam SDK and connects to your machine. That connection goes to `viam-server`, not to the cloud app. The cloud app helps your script locate the machine and authenticate, but once the connection is established, every control API call goes directly to `viam-server` on the robot: moving the arm, reading the camera, calling the vision service. |
There was a problem hiding this comment.
Might be better not to reference phase 4 here, and instead just focus on the api?
|
|
||
| This is why the workshop asks you to make changes in the app rather than by editing a file on the robot directly. The app's CONFIGURE tab is the only place you need to look to know what a machine will do. | ||
|
|
||
| Open the **CONFIGURE** tab now and find the JSON view toggle near the top of the panel. Switching to JSON shows you the exact document that `viam-server` receives, the same resources you see as cards in the builder view, expressed as the config it consumes. |
There was a problem hiding this comment.
| Open the **CONFIGURE** tab now and find the JSON view toggle near the top of the panel. Switching to JSON shows you the exact document that `viam-server` receives, the same resources you see as cards in the builder view, expressed as the config it consumes. | |
| Open the **CONFIGURE** tab now and find the JSON view toggle near the top of the panel. Switching to JSON shows you the exact document that `viam-server` receives, the same resources you see as cards in the builder view. |
|
|
||
| Everything a Viam machine does, hardware and software alike, is modeled as a **resource**. Each resource has a name you choose (like `arm-1`), an API that describes what kind of thing it is (an arm, a camera, a vision service), and a model that identifies the specific implementation. | ||
|
|
||
| Open the **CONFIGURE** tab and find `arm-1`. Next to its name you will see a triplet in the form `namespace:family:model`, for example `viam:ufactory:xArm6`. That triplet tells `viam-server` exactly which code to run for this resource: who published it (`namespace`), what family of hardware it belongs to (`family`), and the specific model (`model`). |
There was a problem hiding this comment.
should we use module instead of family?
| - **Python 3.10 or newer.** Install it with [uv](https://docs.astral.sh/uv/getting-started/installation/) (recommended) or from [python.org](https://www.python.org/downloads/). | ||
| - **The Viam Python SDK.** The companion `scripts/` project already declares `viam-sdk`, so `uv run` installs it for you in Phase 4. See the [Python SDK docs](https://python.viam.dev/) for reference. Pip works too if you prefer it. | ||
| - **A working terminal** on the machine you will run the Phase 4 and Phase 5 scripts from, typically your laptop rather than the robot's Meerkat. | ||
| - **A Viam account with an accessible machine.** Log in at [app.viam.com](https://app.viam.com), open your machine, and confirm the green **Live** indicator before you begin. |
There was a problem hiding this comment.
Should we add something about accessing the machine with the resources in the machine config?
| Resources split into two kinds: | ||
|
|
||
| - **Components** represent physical hardware. `arm-1`, `gripper-1`, and `cam-1` are all components: each one wraps a piece of hardware and exposes a standard API for it (an arm API with move commands, a camera API that returns images, and so on). | ||
| - **Services** represent software capabilities that consume other resources rather than hardware directly. A motion service plans collision-free paths for an arm. Later, in Phase 5, you configure a `shape-detector` vision service that reads frames from `cam-1` and finds blocks by shape, and a `vision-segment` service (model `detections-to-segments`) that takes those detections and turns them into point cloud segments the motion planner can grasp. Neither service is a physical thing; each one composes other resources into a new capability. |
There was a problem hiding this comment.
| - **Services** represent software capabilities that consume other resources rather than hardware directly. A motion service plans collision-free paths for an arm. Later, in Phase 5, you configure a `shape-detector` vision service that reads frames from `cam-1` and finds blocks by shape, and a `vision-segment` service (model `detections-to-segments`) that takes those detections and turns them into point cloud segments the motion planner can grasp. Neither service is a physical thing; each one composes other resources into a new capability. | |
| - **Services** represent software tasks or capabilities. A motion service plans collision-free paths for an arm. Later, in Phase 5, you configure a `shape-detector` vision service that reads frames from `cam-1` and finds blocks by shape, and a `vision-segment` service (model `detections-to-segments`) that takes those detections and turns them into point cloud segments the motion planner can grasp. Neither service is a physical thing; each one composes other resources into a new capability. |
| - **Components** represent physical hardware. `arm-1`, `gripper-1`, and `cam-1` are all components: each one wraps a piece of hardware and exposes a standard API for it (an arm API with move commands, a camera API that returns images, and so on). | ||
| - **Services** represent software capabilities that consume other resources rather than hardware directly. A motion service plans collision-free paths for an arm. Later, in Phase 5, you configure a `shape-detector` vision service that reads frames from `cam-1` and finds blocks by shape, and a `vision-segment` service (model `detections-to-segments`) that takes those detections and turns them into point cloud segments the motion planner can grasp. Neither service is a physical thing; each one composes other resources into a new capability. | ||
|
|
||
| Open the **CONTROL** tab and look at the cards laid out for your machine. Each card corresponds to one resource. The arm, gripper, and camera cards let you jog hardware directly; any service card lets you exercise a capability that is built on top of that hardware. |
There was a problem hiding this comment.
| Open the **CONTROL** tab and look at the cards laid out for your machine. Each card corresponds to one resource. The arm, gripper, and camera cards let you jog hardware directly; any service card lets you exercise a capability that is built on top of that hardware. | |
| Open the **CONTROL** tab and look at the cards laid out for your machine. Each card corresponds to one resource. The arm, gripper, and camera cards let you interact with hardware directly; any service card lets you exercise a capability that is built on top of that hardware. |
| Open the **CONTROL** tab and look at the cards laid out for your machine. Each card corresponds to one resource. The arm, gripper, and camera cards let you jog hardware directly; any service card lets you exercise a capability that is built on top of that hardware. | ||
|
|
||
| {{< alert title="Foreshadowing" color="note" >}} | ||
| You will not configure `shape-detector` or `vision-segment` until Phase 5. For now, just notice the pattern: a service is defined by what other resources it depends on, not by hardware it owns. |
There was a problem hiding this comment.
| You will not configure `shape-detector` or `vision-segment` until Phase 5. For now, just notice the pattern: a service is defined by what other resources it depends on, not by hardware it owns. | |
| You will not configure `shape-detector` or `vision-segment` until Phase 5. For now, just notice the pattern: a service is defined by what resources it depends on or features it provides, not by hardware it owns. |
|
|
||
| The `viam:ufactory:xArm6` arm you saw in the CONFIGURE tab is module-provided. In Phase 2, the moment you add that arm to your config, `viam-server` recognizes it does not have `viam:ufactory:xArm6` built in, downloads the module that provides it from the Viam registry, and starts it. You will be able to watch that download and start happen live in the LOGS tab. | ||
|
|
||
| Open the **CONFIGURE** tab again and compare `arm-1`'s namespace to the namespace on any resource marked `rdk` (if you see one). A namespace of `rdk` means the model ships inside `viam-server` itself; any other namespace, like `viam` or `erh`, means the model arrives as a module. |
There was a problem hiding this comment.
if its not in the premade machine config, consider removing
|
|
||
| <!-- ASSET P0 diagram-dependency-graph (DIAGRAM): cam-1 -> shape-detector -> vision-segment; arm-1 -> gripper-1 + five pose switches; motion service --> | ||
|
|
||
| Resources can depend on each other. A gripper attaches to an arm, so `gripper-1`'s config points at `arm-1`. A vision service reads from a camera, so it depends on `cam-1`. `viam-server` reads these dependencies out of your config and builds a graph, then starts resources in an order that respects it: a resource cannot start until everything it depends on has started. |
There was a problem hiding this comment.
| Resources can depend on each other. A gripper attaches to an arm, so `gripper-1`'s config points at `arm-1`. A vision service reads from a camera, so it depends on `cam-1`. `viam-server` reads these dependencies out of your config and builds a graph, then starts resources in an order that respects it: a resource cannot start until everything it depends on has started. | |
| Resources can depend on each other. A gripper attaches to an arm, so `gripper-1`'s config points at `arm-1`. A vision service reads from a camera, so it depends on `cam-1`. `viam-server` reads these dependencies out of your config and builds a dependency graph, then starts resources in an order that respects it: a resource cannot start until everything it depends on has started. |
| Open the **LOGS** tab and look at the startup sequence the next time the machine restarts (you do not need to trigger one now). The order resources come online in the log follows the dependency graph, not the order they appear in the config file. | ||
|
|
||
| {{< alert title="Check yourself" color="note" >}} | ||
| Before moving to Phase 2, make sure you can answer the three questions from the top of this page: name the three layers and which one your code talks to, what separates a component from a service, and why adding the arm triggers a module download. If any answer feels shaky, re-skim the relevant section above. |
There was a problem hiding this comment.
probably should swap out the third question
| Before moving to Phase 2, make sure you can answer the three questions from the top of this page: name the three layers and which one your code talks to, what separates a component from a service, and why adding the arm triggers a module download. If any answer feels shaky, re-skim the relevant section above. | ||
| {{< /alert >}} | ||
|
|
||
| {{< workshop-nav >}} |
There was a problem hiding this comment.
Might look prettier to have "You have completed The Mental Model, lets move on to Configuring Resources! instead of the generic next
| - **Components** represent physical hardware. `arm-1`, `gripper-1`, and `cam-1` are all components: each one wraps a piece of hardware and exposes a standard API for it (an arm API with move commands, a camera API that returns images, and so on). | ||
| - **Services** represent software capabilities that consume other resources rather than hardware directly. A motion service plans collision-free paths for an arm. Later, in Phase 5, you configure a `shape-detector` vision service that reads frames from `cam-1` and finds blocks by shape, and a `vision-segment` service (model `detections-to-segments`) that takes those detections and turns them into point cloud segments the motion planner can grasp. Neither service is a physical thing; each one composes other resources into a new capability. | ||
|
|
||
| Open the **CONTROL** tab and look at the cards laid out for your machine. Each card corresponds to one resource. The arm, gripper, and camera cards let you jog hardware directly; any service card lets you exercise a capability that is built on top of that hardware. |
There was a problem hiding this comment.
Section 2 says the machine config should be empty, I think this implies students would have something to play with, should this move to section 2?
| | `gripper-1` | `rdk:component:gripper` | `viam:ufactory:gripper` | | ||
| | `cam-1` | `rdk:component:camera` | `viam:camera:realsense` | | ||
|
|
||
| You will not touch the vision service in this phase. The `shape-detector` and `vision-segment` services that let the machine find blocks by shape come later, in Phase 5, right before you write the code that calls them. For now, focus on the hardware. |
There was a problem hiding this comment.
| You will not touch the vision service in this phase. The `shape-detector` and `vision-segment` services that let the machine find blocks by shape come later, in Phase 5, right before you write the code that calls them. For now, focus on the hardware. |
| <!-- ASSET P0 logs-xarm-module-start (UI+): LOGS showing the viam:ufactory module download + start (the module-download moment) --> | ||
| <!-- ASSET P1 configure-arm-attributes (UI): arm-1 card with host / port 502 --> | ||
|
|
||
| On the **CONFIGURE** tab, add a new component. In the add-component dialog, search for `xArm6` and select the `viam:ufactory:xArm6` result. Name the component `arm-1`. |
There was a problem hiding this comment.
they renamed them "blocks" recently, + > Blocks -> search
| <!-- ASSET P0 logs-xarm-module-start (UI+): LOGS showing the viam:ufactory module download + start (the module-download moment) --> | ||
| <!-- ASSET P1 configure-arm-attributes (UI): arm-1 card with host / port 502 --> | ||
|
|
||
| On the **CONFIGURE** tab, add a new component. In the add-component dialog, search for `xArm6` and select the `viam:ufactory:xArm6` result. Name the component `arm-1`. |
There was a problem hiding this comment.
| On the **CONFIGURE** tab, add a new component. In the add-component dialog, search for `xArm6` and select the `viam:ufactory:xArm6` result. Name the component `arm-1`. | |
| On the **CONFIGURE** tab, add a new component. In the add-component dialog, search for `xArm6` and select the `ufactory/xArm6` result and press Add to machine. Name the component `arm-1`. |
|
|
||
| You can leave `speed_degs_per_sec` and `acceleration_degs_per_sec_per_sec` unset for now; both are optional and default to safe values. | ||
|
|
||
| Save the config. This is the moment from Phase 1 made concrete: `viam-server` does not have `viam:ufactory:xArm6` built in, so it fetches the `viam:ufactory` module from the Viam registry and starts it. Open the **LOGS** tab and watch it happen: a log line for the module download, then one for the module starting, then `arm-1` itself coming online. The whole sequence usually takes well under a minute. |
There was a problem hiding this comment.
the "made concrete" sounds funny, but better phrasing is escaping me
| Save the config. This is the moment from Phase 1 made concrete: `viam-server` does not have `viam:ufactory:xArm6` built in, so it fetches the `viam:ufactory` module from the Viam registry and starts it. Open the **LOGS** tab and watch it happen: a log line for the module download, then one for the module starting, then `arm-1` itself coming online. The whole sequence usually takes well under a minute. | ||
|
|
||
| {{< alert title="Two components, one module" color="note" >}} | ||
| The `viam:ufactory` module provides both the arm model and the gripper model you configure next. You only pay the download cost once. |
There was a problem hiding this comment.
| The `viam:ufactory` module provides both the arm model and the gripper model you configure next. You only pay the download cost once. | |
| The `viam:ufactory` module provides both the arm model and the gripper model you configure next. Viam Server only has to download the module once to access both models. |
|
|
||
| <!-- ASSET P2 configure-gripper (UI): gripper-1 config with arm: "arm-1" --> | ||
|
|
||
| Add another component. In the add-component dialog, search for the `viam:ufactory:gripper` model and select it. This model comes from the same `viam:ufactory` module as the arm. Name it `gripper-1`. |
There was a problem hiding this comment.
| Add another component. In the add-component dialog, search for the `viam:ufactory:gripper` model and select it. This model comes from the same `viam:ufactory` module as the arm. Name it `gripper-1`. | |
| Add another component. In the add-component dialog, search for the `ufactory/gripper` model and select it. This model comes from the same `viam:ufactory` module as the arm. Name it `gripper-1`. |
|
|
||
| <!-- ASSET P2 configure-camera (UI): cam-1 realsense config (sensors color+depth, 640x480) --> | ||
|
|
||
| Add a third component. In the add-component dialog, search for `realsense` and select the `viam:camera:realsense` result. Name it `cam-1`. |
There was a problem hiding this comment.
| Add a third component. In the add-component dialog, search for `realsense` and select the `viam:camera:realsense` result. Name it `cam-1`. | |
| Add a third component. In the add-component dialog, search for `realsense` and select the `realsense/realsesne` result. Name it `cam-1`. |
| Add a third component. In the add-component dialog, search for `realsense` and select the `viam:camera:realsense` result. Name it `cam-1`. | ||
|
|
||
| Set the following attributes: | ||
|
|
There was a problem hiding this comment.
does this need the SN? or does it just grab the first one it finds if non exist?
| On the camera card, confirm you get a live feed: | ||
|
|
||
| {{< checkpoint >}} | ||
| The camera card shows a live color stream from `cam-1`. If depth is available as a separate view, confirm that stream updates too. If the card is blank, check the LOGS tab for a camera error before moving on. |
There was a problem hiding this comment.
the depth should be there? can we word it differently?
| The camera card shows a live color stream from `cam-1`. If depth is available as a separate view, confirm that stream updates too. If the card is blank, check the LOGS tab for a camera error before moving on. | ||
| {{< /checkpoint >}} | ||
|
|
||
| On the arm card, jog a joint with the sliders and confirm the arm moves. Then select **Get end position** and confirm it returns x, y, and z coordinates. |
There was a problem hiding this comment.
I think this needs a warning that the arm is about to move and we need to check the estop, and i think joint sliders can cause collisions
| With a block between the fingers, **Grab** closes the fingers and the gripper holds the block without dropping it. **Open** releases the block. This grab-and-release is the same action your Python code performs later in the workshop when it picks a block and drops it in a bin. If your gripper card also shows a holding status indicator, it now reads true while the block is held and false once the gripper is open and empty. | ||
| {{< /checkpoint >}} | ||
|
|
||
| ## The 3D scene tab |
There was a problem hiding this comment.
I think we skipped the frame part for these to connect to eachother
| Set one attribute: | ||
|
|
||
| - `arm`: `"arm-1"` | ||
|
|
There was a problem hiding this comment.
i think we need to add a frame connecting the gripper to the arm
| - `width_px`: `640` | ||
| - `height_px`: `480` | ||
|
|
||
| Save the config and confirm in the **LOGS** tab that `viam-server` downloads the `viam:camera-realsense` module and starts `cam-1`. This is a separate module from `viam:ufactory`, since it comes from a different publisher and family. |
There was a problem hiding this comment.
i think we need to add a frame connecting the camera to the arm
| Jog joint 1 with the arm card's sliders and watch the 3D scene as the arm turns. The `cam-1` frame moves with the arm, because the camera is mounted on the wrist rather than fixed in the workspace. | ||
|
|
||
| {{< alert title="Why this matters later" color="note" >}} | ||
| Because the camera is wrist-mounted, every detection it makes is relative to wherever the arm happens to be pointed at that moment. In Phase 5 you will detect from a single known pose so that "camera space" always means the same thing. Notice that pose here; you will meet it again as the "detect from home" rule. |
There was a problem hiding this comment.
i think detection and camera space feel weird and might need to replaced with something slightly more clear for the reader?
| Because the camera is wrist-mounted, every detection it makes is relative to wherever the arm happens to be pointed at that moment. In Phase 5 you will detect from a single known pose so that "camera space" always means the same thing. Notice that pose here; you will meet it again as the "detect from home" rule. | ||
| {{< /alert >}} | ||
|
|
||
| The exact camera mounting offset used to transform its frame into the arm's frame comes from your hardware setup guide or the pre-provisioned config, not from anything you configure in this phase. For now, just confirm the relationship: the frame exists, and it tracks the arm. |
There was a problem hiding this comment.
maybe consider moving this to later in perception guided picking
?
| The exact camera mounting offset used to transform its frame into the arm's frame comes from your hardware setup guide or the pre-provisioned config, not from anything you configure in this phase. For now, just confirm the relationship: the frame exists, and it tracks the arm. | ||
|
|
||
| ## Check your work | ||
|
|
There was a problem hiding this comment.
maybe list what should be in the config and what should be seen in the 3d view before referencing the json. might be able to just render it here but pull from github so it always stays up to date
|
|
||
| If you want to compare your configuration against a known-good version, the companion repo has a reference copy at [machine-fragment.json](https://github.com/viam-devrel/pick-and-place/blob/main/config/machine-fragment.json). Use it to check your work, not to import over what you just built by hand. | ||
|
|
||
| With `arm-1`, `gripper-1`, and `cam-1` all live and verified, you are ready for Phase 3, where you save a set of fixed arm poses and prove the full hardware sequence works before perception enters the picture. |
There was a problem hiding this comment.
| With `arm-1`, `gripper-1`, and `cam-1` all live and verified, you are ready for Phase 3, where you save a set of fixed arm poses and prove the full hardware sequence works before perception enters the picture. | |
| With `arm-1`, `gripper-1`, and `cam-1` components all live and verified, you are ready for [Phase 3, where you move the arm through a set of arm poses](03-static-positions.md). |

Summary
Introduces a reusable, multi-page sequential workshop format in the existing
docs/tutorials/section, seeded by a vision-guided pick-and-place workshop. Everything ships asdraft: true.checkpoint(verify-before-continuing callout) andworkshop-nav(renders "Phase N of M" + prev/next from frontmatter). Both inline their CSS once per page via the existingnotice-style Scratch guard.layouts/docs/tutorials.htmlnow renders one card per workshop (viaworkshop_overview) and excludes the individual phase pages from the card grid._index.md) and a fully authored exemplar phase (03-static-positions.md); phases 01/02/04/05 are structured stubs; plus a reusable headless_phase-template.md..htmltest.ymlignores the not-yet-created companion repo so link-checking stays green.Scope (v1)
The reusable format end to end + overview + one fully-written exemplar phase + structured stubs. Intentionally out of scope (each flagged inline as a TODO):
viam-devrel/pick-and-place(machine fragment, starter/reference scripts)Verification
prettier --check,markdownlint, andvaleall clean (0 vale errors/warnings/suggestions across 7 files).make build-prodwas not run locally (production PostCSS pipeline could not run in the authoring environment) — relying on CI for the production build.Test plan
/tutorials/shows a single "Pick-and-Place Workshop" card (note: staging/prod card grid is Typesense-driven; phase-page exclusion there is a tracked follow-up)/tutorials/pick-and-place/overview and Phase 3 render; checkpoints styled; prev/next work; sidebar lists phases 1→5_phase-template.mddoes not render as a pageReview the tutorial pages here: https://deploy-preview-5104--viam-docs.netlify.app/tutorials/pick-and-place/
Preview:

🤖 Generated with Claude Code